├── .gitignore ├── README.md ├── api.js ├── app.js ├── blockchain.js ├── contracts └── nft_simple.wasm ├── examples └── nft_deploy │ ├── README.MD │ ├── deploy_tokens.js │ └── token_types.js ├── near-api-server.config.json ├── near-api-ui ├── README.md ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt └── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── assets │ ├── android-chrome-192x192.png │ ├── android-chrome-384x384.png │ ├── apple-touch-icon.png │ ├── explorer-bg.svg │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── icon-network-right.svg │ ├── logo-black.svg │ ├── logo-white.svg │ ├── logo.svg │ ├── mstile-150x150.png │ └── site.webmanifest │ ├── config.js │ ├── fonts │ ├── 389947_6_0.eot │ ├── 389947_6_0.ttf │ ├── 389947_6_0.woff │ └── 389947_6_0.woff2 │ ├── index.css │ ├── index.html │ ├── index.js │ ├── logo.svg │ ├── reportWebVitals.js │ ├── setupTests.js │ └── utils.js ├── package.json ├── token.js └── user.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | package-lock.json 4 | near-api-server.config.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NEAR REST API SERVER 2 | 3 | > Interact with the NEAR blockchain using a simple REST API. 4 | 5 | ###### Live Demo: 6 | * [REST API Endpoint for NEAR Testnet](https://rest.nearspace.info) 7 | * [Web Console for `view`/`call` requests](https://api.nearspace.info) 8 | 9 | --- 10 | 11 | ## Overview 12 | 13 | _Click on a route for more information and examples_ 14 | 15 | | Route | Method | Description | 16 | | ------------------------------------------ | ------ | --------------------------------------------------------------------------------------------------------------------------- | 17 | | **CONTRACTS** | | | 18 | | [`/deploy`](#deploy) | POST | Deploys a smart contract on NEAR. | 19 | | [`/view`](#view) | POST | Performs a smart contract **view** call with no gas burnt. | 20 | | [`/call`](#call) | POST | Performs a smart contract **change** call that burns gas. | 21 | | | | | 22 | | **UTILS** | | | 23 | | [`/init`](#init) | POST | Initializes the master account and updates `near-api-server-config.json` | 24 | | [`/create_user`](#create_user) | POST | Creates a NEAR [sub-account](https://docs.near.org/docs/concepts/account#subaccounts) and stores credentials in `/storage`. | 25 | | [`/parse_seed_phrase`](#parse_seed_phrase) | POST | Displays public and private key pair from a given seed phrase. | 26 | | [`/balance`](#balance) | GET | Displays account balance. | 27 | | [`/keypair`](#keypair) | GET | Generates Ed25519 key pair. | 28 | | [`/explorer`](#explorer) | POST | Run SELECT query in NEAR explorer database. | 29 | | | | | 30 | | **NFT EXAMPLE** | | | 31 | | [`/mint_nft`](#mint_nft) | POST | Mints an NFT for a given contract. | 32 | | [`/transfer_nft`](#transfer_nft) | POST | Transfers NFT ownership to a specified account. | 33 | | [`/view_nft`](#view_nft) | POST | Returns owner, metadata, and approved account IDs for a given token ID. | 34 | 35 | --- 36 | 37 | ## Requirements 38 | 39 | - [NEAR Account](https://docs.near.org/concepts/basics/account) _(with access to private key or seed phrase)_ 40 | - [Node.js](https://nodejs.org/en/download/package-manager/) 41 | - [npm](https://www.npmjs.com/get-npm) or [Yarn](https://yarnpkg.com/getting-started/install) 42 | - API request tool such as [Postman](https://www.postman.com/downloads/) 43 | 44 | --- 45 | 46 | ## Setup 47 | 48 | 1. Clone repository 49 | 50 | ```bash 51 | git clone git@github.com:near-examples/near-api-server.git 52 | ``` 53 | 54 | 2. Install dependencies 55 | 56 | ```bash 57 | npm install 58 | ``` 59 | 60 | 3. Configure `near-api-server.config.json` 61 | 62 | Default settings: 63 | 64 | ```json 65 | { 66 | "server_host": "localhost", 67 | "server_port": 3000, 68 | "rpc_node": "https://rpc.testnet.near.org", 69 | "init_disabled": true 70 | } 71 | ``` 72 | 73 | _**Note:** `init_disabled` determines if params can be changed via `/init` route._ 74 | 75 | 4. Start server 76 | 77 | ```bash 78 | node app 79 | ``` 80 | 81 | --- 82 | 83 | # Contracts 84 | 85 | ## `/deploy` 86 | 87 | > _Deploys a smart contract to the NEAR blockchain based on the wasm file located in `/contracts` folder._ 88 | 89 | **Method:** **`POST`** 90 | 91 | | Param | Description | 92 | | -------------------------------- | ------------------------------------------------------------------------------------ | 93 | | `account_id` | _Account id that you will be deploying the contract to._ | 94 | | `seed_phrase` _OR_ `private_key` | _Seed phrase OR private key of the account id above._ | 95 | | `contract` | _wasm file of compiled contract located in the `/contracts` folder of this project._ | 96 | 97 | _**Note:** Use [`near login`](https://docs.near.org/tools/near-cli#near-login) to save your key pair to your local machine._ 98 | 99 | Example: 100 | 101 | ```json 102 | { 103 | "account_id": "example.testnet", 104 | "seed_phrase": "witch collapse practice feed shame open despair creek road again ice least", 105 | "contract": "nft_simple.wasm" 106 | } 107 | ``` 108 | 109 |
110 | Example Response: 111 |

112 | 113 | ```json 114 | { 115 | "status": { 116 | "SuccessValue": "" 117 | }, 118 | "transaction": { 119 | "signer_id": "example.testnet", 120 | "public_key": "ed25519:Cgg4i7ciid8uG4K5Vnjzy5N4PXLst5aeH9ApRAUA3y8U", 121 | "nonce": 5, 122 | "receiver_id": "example.testnet", 123 | "actions": [ 124 | { 125 | "DeployContract": { 126 | "code": "hT9saWV3aok50F8JundSIWAW+lxOcBOns1zenB2fB4E=" 127 | } 128 | } 129 | ], 130 | "signature": "ed25519:3VrppDV8zMMRXErdBJVU9MMbbKZ4SK1pBZqXoyw3oSSiXTeyR2W7upNhhZPdFJ1tNBr9h9SnsTVeBm5W9Bhaemis", 131 | "hash": "HbokHoCGcjGQZrz8yU8QDqBeAm5BN8iPjaSMXu7Yp2KY" 132 | }, 133 | "transaction_outcome": { 134 | "proof": [ 135 | { 136 | "hash": "Dfjn2ro1dXrPqgzd5zU7eJpCMKnATm295ceocX73Qiqn", 137 | "direction": "Right" 138 | }, 139 | { 140 | "hash": "9raAgMrEmLpL6uiynMAi9rykJrXPEZN4WSxLJUJXbipY", 141 | "direction": "Right" 142 | } 143 | ], 144 | "block_hash": "B64cQPDNkwiCcN3SGXU2U5Jz5M9EKF1hC6uDi4S15Fb3", 145 | "id": "HbokHoCGcjGQZrz8yU8QDqBeAm5BN8iPjaSMXu7Yp2KY", 146 | "outcome": { 147 | "logs": [], 148 | "receipt_ids": ["D94GcZVXE2WgPGuaJPJq8MdeEUidrN1FPkuU75NXWm7X"], 149 | "gas_burnt": 1733951676474, 150 | "tokens_burnt": "173395167647400000000", 151 | "executor_id": "example.testnet", 152 | "status": { 153 | "SuccessReceiptId": "D94GcZVXE2WgPGuaJPJq8MdeEUidrN1FPkuU75NXWm7X" 154 | } 155 | } 156 | }, 157 | "receipts_outcome": [ 158 | { 159 | "proof": [ 160 | { 161 | "hash": "3HLkv7KrQ9LPptX658QiwkFagv8NwjcxF6ti15Een4uh", 162 | "direction": "Left" 163 | }, 164 | { 165 | "hash": "9raAgMrEmLpL6uiynMAi9rykJrXPEZN4WSxLJUJXbipY", 166 | "direction": "Right" 167 | } 168 | ], 169 | "block_hash": "B64cQPDNkwiCcN3SGXU2U5Jz5M9EKF1hC6uDi4S15Fb3", 170 | "id": "D94GcZVXE2WgPGuaJPJq8MdeEUidrN1FPkuU75NXWm7X", 171 | "outcome": { 172 | "logs": [], 173 | "receipt_ids": [], 174 | "gas_burnt": 1733951676474, 175 | "tokens_burnt": "173395167647400000000", 176 | "executor_id": "example.testnet", 177 | "status": { 178 | "SuccessValue": "" 179 | } 180 | } 181 | } 182 | ] 183 | } 184 | ``` 185 | 186 |

187 |
188 | 189 | --- 190 | 191 | ## `/view` 192 | 193 | > _Performs a smart contract view call that is free of charge (no gas burnt)._ 194 | 195 | **Method:** **`POST`** 196 | 197 | | Param | Description | 198 | | ---------- | ----------------------------------------------------------------------------------------- | 199 | | `contract` | _Account id of the smart contract you are calling._ | 200 | | `method` | _Name of the public method on the contract you are calling._ | 201 | | `params` | _Arguments the method of the contract takes. Pass an empty object if no args are needed._ | 202 | 203 | Example: 204 | 205 | ```json 206 | { 207 | "contract": "inotel.pool.f863973.m0", 208 | "method": "get_accounts", 209 | "params": { "from_index": 0, "limit": 5 } 210 | } 211 | ``` 212 | 213 |
214 | Example Response: 215 |

216 | 217 | ```json 218 | [ 219 | { 220 | "account_id": "ino.lockup.m0", 221 | "unstaked_balance": "0", 222 | "staked_balance": "2719843984800963837328608365424", 223 | "can_withdraw": true 224 | }, 225 | { 226 | "account_id": "ino.testnet", 227 | "unstaked_balance": "2", 228 | "staked_balance": "3044983795632859169857527919579", 229 | "can_withdraw": true 230 | }, 231 | { 232 | "account_id": "ino.stakewars.testnet", 233 | "unstaked_balance": "2", 234 | "staked_balance": "21704174266817478470830456026", 235 | "can_withdraw": true 236 | }, 237 | { 238 | "account_id": "ds4.testnet", 239 | "unstaked_balance": "3", 240 | "staked_balance": "10891355794195012441764921", 241 | "can_withdraw": true 242 | }, 243 | { 244 | "account_id": "32oijafsiodjfas.testnet", 245 | "unstaked_balance": "3", 246 | "staked_balance": "383757424103247547511904666", 247 | "can_withdraw": true 248 | } 249 | ] 250 | ``` 251 | 252 |

253 |
254 | 255 | --- 256 | 257 | ## `/call` 258 | 259 | > _Performs a smart contract call that changes state and burns gas._ 260 | 261 | **Method:** **`POST`** 262 | 263 | | Param | Description | 264 | | -------------------------------- | --------------------------------------------------------------------------------------------------------------------- | 265 | | `account_id` | _Account id that will be performing the call and will be charged for gas and attached tokens / deposit._ | 266 | | `seed_phrase` _OR_ `private_key` | _Seed phrase OR private key of the account id above._ | 267 | | `contract` | _Account id of the smart contract you will be calling._ | 268 | | `method` | _Public method on the smart contract that you will be calling._ | 269 | | `params` | _Arguments the method of the contract takes. Pass an empty object if no args are needed._ | 270 | | `attached_gas` | _Amount of gas you will be attaching to the call in [TGas](https://docs.near.org/docs/concepts/gas#thinking-in-gas)._ | 271 | | `attached_tokens` | _Amount of tokens to be sent to the contract you are calling in yoctoNEAR (10^-24 NEAR)._ | 272 | 273 | _**Note:** Use [`near login`](https://docs.near.org/docs/tools/near-cli#near-login) to save your key pair to your local machine._ 274 | 275 | Example: 276 | 277 | ```json 278 | { 279 | "account_id": "example.testnet", 280 | "private_key": "2Kh6PJjxH5PTTsVnYqtgnnwXHeafvVGczDXoCb33ws8reyq8J4oBYix1KP2ugRQ7q9NQUyPcVFTtbSG3ARVKETfK", 281 | "contract": "guest-book.testnet", 282 | "method": "addMessage", 283 | "params": { "text": "Hello World" }, 284 | "attached_gas": "100000000000000", 285 | "attached_tokens": "0" 286 | } 287 | ``` 288 | 289 | 290 |
291 | Example Response: 292 |

293 | 294 | ```json 295 | { 296 | "status": { 297 | "SuccessValue": "" 298 | }, 299 | "transaction": { 300 | "signer_id": "example.testnet", 301 | "public_key": "ed25519:ASZEids5Qa8XMHX2S7LRL4bQRczi4YuMWXSM7S1HE5b", 302 | "nonce": 4, 303 | "receiver_id": "guest-book.testnet", 304 | "actions": [ 305 | { 306 | "FunctionCall": { 307 | "method_name": "addMessage", 308 | "args": "eyJ0ZXh0IjoiSGVsbG8gV29ybGQifQ==", 309 | "gas": 100000000000000, 310 | "deposit": "0" 311 | } 312 | } 313 | ], 314 | "signature": "ed25519:4T9FqsjYBxcitjd5GgHrv3i3hcdcJSNcwwG3jBUgs7zZCZ3uShAK44Hi3oYFefhr8e5UW3LLD49ofRpGXKwGqqot", 315 | "hash": "CniHtfQVzcyVWJaUrQibJyGdhLi5axsjsoSRvvFbJ1jv" 316 | }, 317 | "transaction_outcome": { 318 | "proof": [ 319 | { 320 | "hash": "EkzDGbbBHSAuJcCPmhKSqbnBKyLrMgXkrTEZZZQudHeH", 321 | "direction": "Right" 322 | }, 323 | { 324 | "hash": "36j4PK6fsLChiVTBQnXS1ywVSgJgHo7FtWzd5y5jkK1B", 325 | "direction": "Right" 326 | } 327 | ], 328 | "block_hash": "CUAu2deED8UX4vkerCbsTMR7YkeKt8RQXknYMNrVvM7C", 329 | "id": "CniHtfQVzcyVWJaUrQibJyGdhLi5axsjsoSRvvFbJ1jv", 330 | "outcome": { 331 | "logs": [], 332 | "receipt_ids": ["B7xAYoga5vrKERK7wY7EHa2Z74LaRJwqPsh4esLrKeQF"], 333 | "gas_burnt": 2427992549888, 334 | "tokens_burnt": "242799254988800000000", 335 | "executor_id": "example.testnet", 336 | "status": { 337 | "SuccessReceiptId": "B7xAYoga5vrKERK7wY7EHa2Z74LaRJwqPsh4esLrKeQF" 338 | } 339 | } 340 | }, 341 | "receipts_outcome": [ 342 | { 343 | "proof": [ 344 | { 345 | "hash": "6Uo6BajpAxiraJEv69RwhjYnC86u56cw29vRDB1SV4dv", 346 | "direction": "Right" 347 | } 348 | ], 349 | "block_hash": "Ecq6pK74uiJFKxPTaasYuQcsEznnQjdzMAfsyrBpDo2u", 350 | "id": "B7xAYoga5vrKERK7wY7EHa2Z74LaRJwqPsh4esLrKeQF", 351 | "outcome": { 352 | "logs": [], 353 | "receipt_ids": ["6S6m1TYuVPYovLu9FHGV5oLRnDXeNQ8NhXxYjcr91xAN"], 354 | "gas_burnt": 3766420707221, 355 | "tokens_burnt": "376642070722100000000", 356 | "executor_id": "guest-book.testnet", 357 | "status": { 358 | "SuccessValue": "" 359 | } 360 | } 361 | }, 362 | { 363 | "proof": [ 364 | { 365 | "hash": "2za2YKUhyMfWbeEL7UKZxZcQbAqEmSPgPoYh9QDdeJQi", 366 | "direction": "Left" 367 | }, 368 | { 369 | "hash": "61aHEiTBBbPU8UEXgSQh42TujFkHXQQMSuTh13PLPwbG", 370 | "direction": "Right" 371 | } 372 | ], 373 | "block_hash": "6LfpzvCBkqq7h5uG9VjAHMwSpC3HMMBSAGNGhbrAJzKP", 374 | "id": "6S6m1TYuVPYovLu9FHGV5oLRnDXeNQ8NhXxYjcr91xAN", 375 | "outcome": { 376 | "logs": [], 377 | "receipt_ids": [], 378 | "gas_burnt": 0, 379 | "tokens_burnt": "0", 380 | "executor_id": "example.testnet", 381 | "status": { 382 | "SuccessValue": "" 383 | } 384 | } 385 | } 386 | ] 387 | } 388 | ``` 389 | 390 |

391 |
392 | 393 | --- 394 | 395 | # Utils 396 | 397 | --- 398 | 399 | ## `/init` 400 | 401 | > _Configures `near-api-server.config.json` and creates a master account that stores credentials in this file. This allows for "simple methods" to be called where you won't have to pass as many parameters, primarily the master account id and private key or seed phrase._ 402 | 403 | **ATTN: SERVER MUST BE RESTARTED AFTER CALLING THIS ENDPOINT** 404 | 405 | **Method:** **`POST`** 406 | 407 | | Param | Description | 408 | | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | 409 | | `master_account_id` | _Master account that has full access to the NFT contract below_ | 410 | | `seed_phrase` _OR_ `private_key` | _Seed phrase OR private key of the account id above._ | 411 | | `nft_contract` | _Contract account that has NFT contract deployed to it_ | 412 | | `server_host` | _Public IP address for your API server (localhost is default)_ | 413 | | `server_port` | _(Port your API server will listen on)_ | 414 | | `rpc_node` | _[Network](https://docs.near.org/docs/concepts/networks) your server will be running on (testnet, mainnet, or betanet)_ | 415 | 416 | _**Note:** Use [`near login`](https://docs.near.org/docs/tools/near-cli#near-login) to save your key pair to your local machine._ 417 | 418 | Example: 419 | 420 | ```json 421 | { 422 | "master_account_id": "example.testnet", 423 | "seed_phrase": "seed phrase for master_account_id goes here", 424 | "nft_contract": "nft-contract.example.testnet", 425 | "server_host": "localhost", 426 | "server_port": 3000, 427 | "rpc_node": "https://rpc.testnet.near.org" 428 | } 429 | ``` 430 | 431 | Example Response: 432 | 433 | ```json 434 | { 435 | "text": "Settings updated." 436 | } 437 | ``` 438 | 439 | --- 440 | 441 | ## `/sign_url` 442 | 443 | > _Generates a link to NEAR Wallet with provided transaction details. May be used to redirect user to the wallet and perform a transaction without generation application-specific keys and granting access._ 444 | 445 | **Method:** **`POST`** 446 | 447 | | Param | Description | 448 | | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | 449 | | `account_id` | _Signer Account_ | 450 | | `receiver_id` | _Recipient contract account, may be dApp contract or personal account_ | 451 | | `method` | _Contract method to call. Use `!transfer` to transfer NEAR tokens_ | 452 | | `params` | _Transaction arguments_ | 453 | | `deposit` | _Attached deposit in NEAR_ | 454 | | `gas` | _Attached gas in yoctoNEAR_ | 455 | | `meta` | _Transaction meta. May be empty_ | 456 | | `callback_url` | _URL to redirect user after the transaction. May be empty_ | 457 | | `network` | _Your network: mainnet/testnet_ | 458 | 459 | Example: 460 | 461 | ``` 462 | { 463 | "account_id": "zavodil.testnet", 464 | "receiver_id": "inotel.pool.f863973.m0", 465 | "method": "ping", 466 | "params": {}, 467 | "deposit": 0, 468 | "gas": 30000000000000, 469 | "meta": "", 470 | "callback_url": "", 471 | "network": "testnet" 472 | } 473 | ``` 474 | 475 | Example Response: 476 | 477 | ``` 478 | https://wallet.testnet.near.org/sign?transactions=DwAAAHphdm9kaWwudGVzdG5ldADKei8CC%2BlhIM9GNPitr87eHXpqdnQsCdLD%2B0ADdTJbqwEAAAAAAAAAFgAAAGlub3RlbC5wb29sLmY4NjM5NzMubTCfZPsioMcZCQRg4Uy7rOu4ERg10QV9c415FuXE0VDRRAEAAAACBAAAAHBpbmcCAAAAe30A4FfrSBsAAAAAAAAAAAAAAAAAAAAAAAA%3D&callbackUrl= 479 | ``` 480 | 481 | Approving this url performed a transaction [143c9MNaqXFXuiobjUaQ8FPSBR2ukYbCMzGdPe6HqXEq](https://explorer.testnet.near.org/transactions/143c9MNaqXFXuiobjUaQ8FPSBR2ukYbCMzGdPe6HqXEq) 482 | 483 | 484 | ## `/create_user` 485 | 486 | > _Creates a NEAR [sub-account](https://docs.near.org/docs/concepts/account#subaccounts) using initialized master account and saves credentials to `/storage` directory. Requires [`/init`](#init) configuration with master account._ 487 | 488 | **Note:** _Only letters, digits, and - or \_ separators are allowed._ 489 | 490 | **Method:** **`POST`** 491 | 492 | Example: 493 | 494 | ``` 495 | { 496 | "name" : "satoshi" 497 | } 498 | ``` 499 | 500 | Example Response: 501 | 502 | ```json 503 | { 504 | "text": "Account satoshi.example.testnet created. Public key: ed25519:HW4koiHqLi5WdVHWy9fqBWHbLRrzfmvCiRAUVhMa14T2" 505 | } 506 | ``` 507 | 508 | --- 509 | 510 | ## `/parse_seed_phrase` 511 | 512 | > _Converts seed phrase into public / private key pair._ 513 | 514 | **Method:** **`POST`** 515 | 516 | Example: 517 | 518 | ``` 519 | { 520 | "seed_phrase" : "witch collapse practice feed shame open despair creek road again ice least" 521 | } 522 | ``` 523 | 524 | Example Response: 525 | 526 | ``` 527 | { 528 | "seedPhrase": "witch collapse practice feed shame open despair creek road again ice least", 529 | "secretKey": "ed25519:41oHMLtYygTsgwDzaMdjWRq48Sy9xJsitJGmMxgA9A7nvd65aT8vQwAvRdHi1nruPP47B6pNhW5T5TK8SsqCZmjn", 530 | "publicKey": "ed25519:Cgg4i7ciid8uG4K5Vnjzy5N4PXLst5aeH9ApRAUA3y8U" 531 | } 532 | ``` 533 | 534 | --- 535 | 536 | ## `/balance` 537 | 538 | > _Displays account balance in yoctoNEAR (10^-24 NEAR)._ 539 | 540 | **Method:** **`GET`** 541 | 542 | Example: 543 | 544 | ``` 545 | http://localhost:3000/balance/name.testnet 546 | ``` 547 | 548 | Example Response: 549 | 550 | ``` 551 | 199999959035075000000000000 552 | ``` 553 | 554 | --- 555 | 556 | ## `/keypair` 557 | 558 | > _Generates Ed25519 key pair._ 559 | 560 | **Method:** **`GET`** 561 | 562 | Example: 563 | 564 | ``` 565 | http://localhost:3000/keypair 566 | ``` 567 | 568 | Example Response: 569 | 570 | ``` 571 | { 572 | "public_key": "ed25519:3pNJK3fwP14UEbPjQqgDASwWR4XmbAEQBeNsyThhtNKY", 573 | "private_key": "3s9nVrCU4MER3w9cxXcJM58RGRzFNJnLzo9vgQiNrkuGW3Xp7Up6cYnY4JKQZ7Qp3GhmXckrApRyDPAfzo2oCm8a" 574 | } 575 | ``` 576 | 577 | ## `/explorer` 578 | 579 | > _Run SELECT query in NEAR explorer database._ 580 | 581 | **Method:** **`POST`** 582 | 583 | | Param | Description | 584 | | -------------------------------- | ----------------------------------------------------------- | 585 | | `user` | _Public account, `public_readonly`_ | 586 | | `host` | _NEAR indexer host, `testnet.db.explorer.indexer.near.dev`_ | 587 | | `database` | _Name of the database, `testnet_explorer`_ | 588 | | `password` | _Password, `nearprotocol`_ | 589 | | `port` | _Port, `5432`_ | 590 | | `parameters` | _Array of query parameters, `[]`_ | 591 | | `query` | _Query without tabs, linebreaks and special characters_ | 592 | 593 | Check indexer server credentials on a [github](https://github.com/near/near-indexer-for-explorer/#shared-public-access). 594 | 595 | Example: 596 | 597 | ```json 598 | { 599 | "user": "public_readonly", 600 | "host": "35.184.214.98", 601 | "database": "testnet_explorer", 602 | "password": "nearprotocol", 603 | "port": 5432, 604 | "parameters": ["testnet", 1], 605 | "query": "SELECT * FROM action_receipt_actions WHERE receipt_receiver_account_id = $1 LIMIT $2" 606 | } 607 | ``` 608 | 609 |
610 | Example Response: 611 |

612 | 613 | ```json 614 | [ 615 | { 616 | "receipt_id": "GZMyzjDWPJLjrCuQG82uHj3xRVHwdDnWHH1gCnSBejkR", 617 | "index_in_action_receipt": 0, 618 | "action_kind": "TRANSFER", 619 | "args": { 620 | "deposit": "1273665187500000000" 621 | }, 622 | "receipt_predecessor_account_id": "system", 623 | "receipt_receiver_account_id": "testnet", 624 | "receipt_included_in_block_timestamp": "1619207391172257749" 625 | } 626 | ] 627 | ``` 628 | 629 |

630 |
631 | 632 | --- 633 | 634 | # NFTs 635 | 636 | --- 637 | 638 | ## `/mint_nft` 639 | 640 | > _Mints a new NFT on a specified contract._ 641 | 642 | **Method:** **`POST`** 643 | 644 | ### Standard NFT Minting 645 | 646 | | Param | Description | 647 | | -------------------------------- | ------------------------------------------------------ | 648 | | `token_id` | _ID for new token you are minting_ | 649 | | `metadata` | _Metadata for the new token as a string._ | 650 | | `account_id` | _Account ID for the new token owner._ | 651 | | `seed_phrase` _OR_ `private_key` | _Seed phrase OR private key for the NFT contract._ | 652 | | `nft_contract` | _Account ID for the NFT contract your are minting on._ | 653 | 654 | _**Note:** Use [`near login`](https://docs.near.org/docs/tools/near-cli#near-login) to save your key pair to your local machine._ 655 | 656 | Example: 657 | 658 | ``` 659 | { 660 | "token_id": "EXAMPLE-TOKEN", 661 | "metadata": "https://ipfs.io/ipfs/Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu", 662 | "account_id": "example.testnet", 663 | "private_key": "41oHMLtYygTsgwDzaMdjWRq48Sy9xJsitJGmMxgA9A7nvd65aT8vQwAvRdHi1nruPP47B6pNhW5T5TK8SsqCZmjn", 664 | "contract": "nft.example.near", 665 | } 666 | ``` 667 | 668 | ### Simple NFT Minting 669 | 670 | _Requires [`/init`](#init) configuration with master account._ 671 | 672 | Example: 673 | 674 | ```json 675 | { 676 | "token_id": "EXAMPLE_TOKEN", 677 | "metadata": "https://ipfs.io/ipfs/Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu" 678 | } 679 | ``` 680 | 681 |
682 | Example Response: 683 |

684 | 685 | ```json 686 | [ 687 | { 688 | "token": { 689 | "owner_id": "example.testnet", 690 | "metadata": "https://ipfs.io/ipfs/Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu", 691 | "approved_account_ids": [], 692 | "token_id": "EXAMPLE_TOKEN" 693 | }, 694 | "tx": "Be7tV1h2dvhg53S2rartojeSUbNfQTB7ypuprmb6xRhw" 695 | } 696 | ] 697 | ``` 698 | 699 |

700 |
701 | 702 | _(`tx` is the transaction hash that can be queried in [NEAR Explorer](http://explorer.testnet.near.org))_ 703 | 704 | --- 705 | 706 | ### Batch NFT minting (simple) 707 | 708 | _Requires [`/init`](#init) configuration with master account._ 709 | 710 | Example: 711 | 712 | ```json 713 | { 714 | "token_id": "EXAMPLE_TOKEN_{inc}", 715 | "metadata": "https://ipfs.io/ipfs/Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu", 716 | "min": 31, 717 | "max": 33 718 | } 719 | ``` 720 | 721 | _(This creates `EXAMPLE_TOKEN_31`, `EXAMPLE_TOKEN_32`, & `EXAMPLE_TOKEN_33`)_ 722 | 723 |
724 | Example Response: 725 |

726 | 727 | ```json 728 | [ 729 | { 730 | "tx": "mAL92gb6g6hhubZBRewJk5vSwmmzm2SXmwdAfYqfWcG" 731 | }, 732 | { 733 | "tx": "Dv9h8nWJLujkKpmw58ZR4QwAgPVprb4j5QarDUumoGEX" 734 | }, 735 | { 736 | "tx": "J48F3vALJBbbUguKXp6e16g5vKVwzC2LtVBpsfEVFpYa" 737 | } 738 | ] 739 | ``` 740 | 741 |

742 |
743 | 744 | _(Above response are transaction hashes that can be queried in [NEAR Explorer](http://explorer.testnet.near.org))_ 745 | 746 | --- 747 | 748 | ## `/transfer_nft` 749 | 750 | > _Transfers ownership of NFT from specified contract on behalf of provided `enforce_owner_id` signed with `owner_private_key`._ 751 | 752 | **Method:** **`POST`** 753 | 754 | ### Standard Transfer NFT 755 | 756 | | Param | Description | 757 | | ------------------- | --------------------------------------------------------- | 758 | | `token_id` | _Token ID of the token being transferred_ | 759 | | `receiver_id` | _Account ID taking ownership of the NFT_ | 760 | | `enforce_owner_id` | _Account ID for the account that currently owns the NFT._ | 761 | | `memo` | _Optional message to the new token holder._ | 762 | | `owner_private_key` | _Private key of the `enforce_owner_id`._ | 763 | | `nft_contract` | _NFT contract that the token being transferred is on._ | 764 | 765 | _**Note:** Use [`near login`](https://docs.near.org/docs/tools/near-cli#near-login) to save your key pair to your local machine._ 766 | 767 | Example: 768 | 769 | ```json 770 | { 771 | "token_id": "EXAMPLE-TOKEN", 772 | "receiver_id": "receiver.testnet", 773 | "enforce_owner_id": "example.testnet", 774 | "memo": "Here's a token I thought you might like! :)", 775 | "owner_private_key": "YOUR_PRIVATE_KEY", 776 | "contract": "nft.example.near" 777 | } 778 | ``` 779 | 780 | Example Response: 781 | 782 | ```json 783 | { 784 | "owner_id": "receiver.testnet", 785 | "metadata": "https://ipfs.io/ipfs/Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu", 786 | "approved_account_ids": [], 787 | "tx": "5WdNgmNeA5UNpSMDRXemwJc95MB6J22LcvAaimuN5YzF" 788 | } 789 | ``` 790 | 791 | _(`tx` is the transaction hash that can be queried in [NEAR Explorer](http://explorer.testnet.near.org))_ 792 | 793 | --- 794 | 795 | ### Simple Transfer NFTs 796 | 797 | > _Requires [`/init`](#init) configuration with master account._ 798 | 799 | | Param | Description | 800 | | ------------------ | --------------------------------------------------------- | 801 | | `token_id` | _Token ID of the token being transferred_ | 802 | | `receiver_id` | _Account ID taking ownership of the NFT_ | 803 | | `enforce_owner_id` | _Account ID for the account that currently owns the NFT._ | 804 | | `memo` | _Optional message to new token holder._ | 805 | 806 | Example: 807 | 808 | ```json 809 | { 810 | "token_id": "EXAMPLE-TOKEN", 811 | "receiver_id": "receiver.testnet", 812 | "enforce_owner_id": "example.testnet", 813 | "memo": "Here's a token I thought you might like! :)" 814 | } 815 | ``` 816 | 817 | Example Response: 818 | 819 | ```json 820 | { 821 | "owner_id": "receiver.testnet", 822 | "metadata": "https://ipfs.io/ipfs/Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu", 823 | "approved_account_ids": [], 824 | "tx": "5WdNgmNeA5UNpSMDRXemwJc95MB6J22LcvAaimuN5YzF" 825 | } 826 | ``` 827 | 828 | _(`tx` is the transaction hash that can be queried in [NEAR Explorer](http://explorer.testnet.near.org))_ 829 | 830 | --- 831 | 832 | ## `view_nft` 833 | 834 | ### Standard View NFT 835 | 836 | > _Returns owner, metadata, and approved account IDs for a given token ID._ 837 | 838 | **Method:** **`POST`** 839 | 840 | Example: 841 | 842 | ```json 843 | { 844 | "token_id": "EXAMPLE-TOKEN", 845 | "contract": "nft.example.testnet" 846 | } 847 | ``` 848 | 849 | Example response: 850 | 851 | ```json 852 | { 853 | "owner_id": "example.testnet", 854 | "metadata": "https://ipfs.io/ipfs/Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu", 855 | "approved_account_ids": [] 856 | } 857 | ``` 858 | 859 | --- 860 | 861 | ### Simple View NFT 862 | 863 | > _Receive detailed information about NFT using URL params. Requires [`/init`](#init) configuration with master account._ 864 | 865 | **Method:** **`GET`** 866 | 867 | Example: 868 | 869 | `http://localhost:3000/view_nft/EXAMPLE-TOKEN` 870 | 871 | Example Response: 872 | 873 | ```json 874 | { 875 | "owner_id": "example.testnet", 876 | "metadata": "https://ipfs.io/ipfs/Qme7ss3ARVgxv6rXqVPiikMJ8u2NLgmgszg13pYrDKEoiu", 877 | "approved_account_ids": [] 878 | } 879 | ``` 880 | 881 | --- 882 | 883 | ## Faker data 884 | 885 | > Use the following tags below to use random data for testing purposes. 886 | 887 | - `{username}` 888 | - `{number}` 889 | - `{word}` 890 | - `{words}` 891 | - `{image}` 892 | - `{color}` 893 | 894 | ## Video Presentation 895 | 896 | [![Live App Review 15 - NFT Server Side API](https://img.youtube.com/vi/d71OscmH4cA/0.jpg)](https://youtu.be/d71OscmH4cA) 897 | -------------------------------------------------------------------------------- /api.js: -------------------------------------------------------------------------------- 1 | const CONFIG_PATH = './near-api-server.config.json'; 2 | 3 | module.exports = { 4 | CONFIG_PATH, 5 | 6 | reject: (err) => { 7 | return {error: typeof err === "string" ? err : JSON.stringify(err)}; 8 | }, 9 | notify: (message) => { 10 | return {text: message}; 11 | }, 12 | getNetworkFromRpcNode(rpc_node){ 13 | return rpc_node.replace("https://rpc.", "").replace(".near.org", ""); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | const user = require('./user'); 5 | const token = require('./token'); 6 | const blockchain = require('./blockchain'); 7 | const api = require('./api'); 8 | const faker = require('faker'); 9 | const crypto = require('crypto'); 10 | const CatboxMemory = require('@hapi/catbox-memory'); 11 | const Hapi = require('@hapi/hapi'); 12 | const fs = require('fs'); 13 | const {Client} = require('pg'); 14 | Client.poolSize = 100; 15 | 16 | const settings = JSON.parse(fs.readFileSync(api.CONFIG_PATH, 'utf8')); 17 | const ViewCacheExpirationInSeconds = 10; 18 | const ViewGenerateTimeoutInSeconds = 30; 19 | 20 | const init = async () => { 21 | const server = Hapi.server({ 22 | port: settings.server_port, 23 | host: settings.server_host, 24 | cache: [ 25 | { 26 | name: 'near-api-cache', 27 | provider: { 28 | constructor: CatboxMemory 29 | } 30 | } 31 | ] 32 | }); 33 | 34 | function processRequest(request) { 35 | Object.keys(request.payload).map((key) => { 36 | switch (request.payload[key]) { 37 | case '{username}': 38 | request.payload[key] = faker.internet 39 | .userName() 40 | .replace(/[^0-9a-z]/gi, ''); 41 | break; 42 | case '{color}': 43 | request.payload[key] = faker.internet.color(); 44 | break; 45 | case '{number}': 46 | request.payload[key] = faker.random.number(); 47 | break; 48 | case '{word}': 49 | request.payload[key] = faker.random.word(); 50 | break; 51 | case '{words}': 52 | request.payload[key] = faker.random.words(); 53 | break; 54 | case '{image}': 55 | request.payload[key] = faker.random.image(); 56 | break; 57 | } 58 | }); 59 | 60 | return request; 61 | } 62 | 63 | server.route({ 64 | method: 'GET', 65 | path: '/', 66 | handler: () => { 67 | return api.notify( 68 | 'Welcome to NEAR REST API SERVER (https://github.com/near-examples/near-api-rest-server)! ' + 69 | (!settings.master_account_id 70 | ? 'Please initialize your NEAR account in order to use simple nft mint/transfer methods' 71 | : `Master Account: ${settings.master_account_id}`) 72 | ); 73 | }, 74 | }); 75 | 76 | server.route({ 77 | method: 'POST', 78 | path: '/view', 79 | handler: async (request, h) => { 80 | request = processRequest(request); 81 | 82 | if (request.payload.disabled_cache) { 83 | return blockchain.View( 84 | request.payload.contract, 85 | request.payload.method, 86 | request.payload.params, 87 | request.payload.rpc_node, 88 | request.payload.headers 89 | ); 90 | } else { 91 | request.payload.request_name = "view"; 92 | return replyCachedValue(h, await server.methods.view(request.payload)); 93 | } 94 | } 95 | }); 96 | 97 | server.method( 98 | 'view', 99 | async (params) => blockchain.View( 100 | params.contract, 101 | params.method, 102 | params.params, 103 | params.rpc_node, 104 | params.headers 105 | ), 106 | getServerMethodParams()); 107 | 108 | server.route({ 109 | method: 'POST', 110 | path: '/call', 111 | handler: async (request) => { 112 | request = processRequest(request); 113 | let { 114 | account_id, 115 | private_key, 116 | attached_tokens, 117 | attached_gas, 118 | contract, 119 | method, 120 | params, 121 | network, 122 | rpc_node, 123 | headers 124 | } = request.payload; 125 | return await blockchain.Call( 126 | account_id, 127 | private_key, 128 | attached_tokens, 129 | attached_gas, 130 | contract, 131 | method, 132 | params, 133 | network, 134 | rpc_node, 135 | headers 136 | ); 137 | }, 138 | }); 139 | 140 | server.route({ 141 | method: 'POST', 142 | path: '/init', 143 | handler: async (request) => { 144 | if (settings.init_disabled) { 145 | return api.reject('Method now allowed'); 146 | } 147 | 148 | request = processRequest(request); 149 | let { 150 | master_account_id, 151 | seed_phrase, 152 | private_key, 153 | nft_contract, 154 | server_host, 155 | server_port, 156 | rpc_node, 157 | } = request.payload; 158 | 159 | if (seed_phrase) 160 | private_key = (await user.GetKeysFromSeedPhrase(seed_phrase)).secretKey; 161 | 162 | let response = await blockchain.Init( 163 | master_account_id, 164 | private_key, 165 | nft_contract, 166 | server_host, 167 | server_port, 168 | rpc_node 169 | ); 170 | if (!response.error) { 171 | process.on('SIGINT', function () { 172 | console.log('Stopping server...'); 173 | server.stop({timeout: 1000}).then(async function () { 174 | await server.start(); 175 | }); 176 | }); 177 | } 178 | 179 | return response; 180 | }, 181 | }); 182 | 183 | server.route({ 184 | method: 'POST', 185 | path: '/deploy', 186 | handler: async (request) => { 187 | request = processRequest(request); 188 | let {account_id, private_key, seed_phrase, contract} = request.payload; 189 | 190 | if (seed_phrase) 191 | private_key = (await user.GetKeysFromSeedPhrase(seed_phrase)).secretKey; 192 | 193 | return await blockchain.DeployContract(account_id, private_key, contract); 194 | }, 195 | }); 196 | 197 | server.route({ 198 | method: 'GET', 199 | path: '/view_nft/{token_id}', 200 | handler: async (request, h) => { 201 | request.params.request_name = "view_nft"; 202 | return replyCachedValue(h, await server.methods.viewNFT(request.params)); 203 | }, 204 | }); 205 | 206 | server.method( 207 | 'viewNFT', 208 | async (params) => await token.ViewNFT(params.token_id), 209 | getServerMethodParams()); 210 | 211 | server.route({ 212 | method: 'POST', 213 | path: '/view_nft', 214 | handler: async (request) => { 215 | return await token.ViewNFT( 216 | request.payload.token_id, 217 | request.payload.contract 218 | ); 219 | }, 220 | }); 221 | 222 | server.route({ 223 | method: 'POST', 224 | path: '/create_user', 225 | handler: async (request) => { 226 | request = processRequest(request); 227 | 228 | const name = ( 229 | request.payload.name + 230 | '.' + 231 | settings.master_account_id 232 | ).toLowerCase(); 233 | let account = await user.CreateKeyPair(name); 234 | 235 | let status = await user.CreateAccount(account); 236 | 237 | if (status) 238 | return { 239 | text: `Account ${name} created. Public key: ${account.public_key}`, 240 | }; 241 | else return {text: 'Error'}; 242 | }, 243 | }); 244 | 245 | server.route({ 246 | method: 'POST', 247 | path: '/parse_seed_phrase', 248 | handler: async (request) => { 249 | request = processRequest(request); 250 | 251 | return await user.GetKeysFromSeedPhrase(request.payload.seed_phrase); 252 | }, 253 | }); 254 | 255 | server.route({ 256 | method: 'GET', 257 | path: '/balance/{account_id}', 258 | handler: async (request) => { 259 | return await blockchain.GetBalance(request.params.account_id); 260 | } 261 | }); 262 | 263 | server.route({ 264 | method: 'GET', 265 | path: '/keypair', 266 | handler: async () => { 267 | return await user.GenerateKeyPair(); 268 | } 269 | }); 270 | 271 | server.route({ 272 | method: 'POST', 273 | path: '/mint_nft', 274 | handler: async (request) => { 275 | let {min, max} = request.payload; 276 | 277 | if (!min || !max) min = max = 0; 278 | let response = []; 279 | 280 | request = processRequest(request); 281 | for (let i = min; i <= max; i++) { 282 | const tokenId = request.payload.token_id.replace('{inc}', i); 283 | 284 | let {account_id, private_key, metadata, contract} = request.payload; 285 | 286 | const tx = await token.MintNFT( 287 | tokenId, 288 | metadata, 289 | contract, 290 | account_id, 291 | private_key 292 | ); 293 | 294 | if (tx) { 295 | if (min === max) { 296 | let create_token = await token.ViewNFT(tokenId, contract); 297 | create_token.token_id = tokenId; 298 | response.push({token: create_token, tx: tx}); 299 | } else { 300 | response.push({tx: tx}); 301 | } 302 | } else { 303 | response.push({text: 'Error. Check backend logs.'}); 304 | } 305 | } 306 | 307 | return response; 308 | }, 309 | }); 310 | 311 | server.route({ 312 | method: 'POST', 313 | path: '/transfer_nft', 314 | handler: async (request) => { 315 | request = processRequest(request); 316 | 317 | let { 318 | token_id, 319 | receiver_id, 320 | enforce_owner_id, 321 | memo, 322 | contract, 323 | owner_private_key, 324 | } = request.payload; 325 | 326 | const txStatus = await token.TransferNFT( 327 | token_id, 328 | receiver_id, 329 | enforce_owner_id, 330 | memo, 331 | contract, 332 | owner_private_key 333 | ); 334 | 335 | if (txStatus.error) { 336 | return txStatus; 337 | } else if (txStatus.status.Failure) { 338 | return { 339 | error: 340 | 'Because of some reason transaction was not applied as expected', 341 | }; 342 | } else { 343 | const new_token = await token.ViewNFT(token_id, contract); 344 | if (!new_token) return api.reject('Token not found'); 345 | 346 | new_token.tx = txStatus.transaction.hash; 347 | return new_token; 348 | } 349 | }, 350 | }); 351 | 352 | server.route({ 353 | method: 'GET', 354 | path: '/about', 355 | handler: async () => { 356 | const json = require('./package.json'); 357 | return "NEAR REST API SERVER Ver. " + json.version; 358 | } 359 | }); 360 | 361 | server.route({ 362 | method: 'POST', 363 | path: '/explorer', 364 | handler: async (request) => { 365 | let { 366 | user, 367 | host, 368 | database, 369 | password, 370 | port, 371 | query, 372 | parameters 373 | } = request.payload; 374 | 375 | const client = new Client({ 376 | user, 377 | host, 378 | database, 379 | password, 380 | port, 381 | }); 382 | 383 | if (["104.199.89.51", "35.184.214.98"].includes(host)) { 384 | return api.reject('Please run explorer function only on your own NEAR REST API SERVER instance, https://github.com/near-examples/near-api-rest-server'); 385 | } 386 | 387 | try { 388 | client.connect(); 389 | let response = await client.query(query, parameters); 390 | return response.rows; 391 | } catch (ex) { 392 | return api.reject('Error. ' + ex.message); 393 | } 394 | }, 395 | }); 396 | 397 | server.route({ 398 | method: 'POST', 399 | path: '/sign_url', 400 | handler: async (request) => { 401 | let { 402 | account_id, 403 | method, 404 | params, 405 | deposit, 406 | gas, 407 | receiver_id, 408 | meta, 409 | callback_url, 410 | network 411 | } = request.payload; 412 | 413 | return blockchain.GetSignUrl( 414 | account_id, 415 | method, 416 | params, 417 | deposit, 418 | gas, 419 | receiver_id, 420 | meta, 421 | callback_url, 422 | network 423 | ); 424 | }, 425 | }); 426 | 427 | await server.start(); 428 | console.log('Server running on %s', server.info.uri); 429 | }; 430 | 431 | process.on('unhandledRejection', (err) => { 432 | console.log(err); 433 | process.exit(1); 434 | }); 435 | 436 | const getServerMethodParams = () => { 437 | return { 438 | generateKey: (params) => { 439 | let hash = crypto.createHash('sha1'); 440 | hash.update(JSON.stringify(params)); 441 | return hash.digest('base64'); 442 | }, 443 | cache: { 444 | cache: 'near-api-cache', 445 | expiresIn: ViewCacheExpirationInSeconds * 1000, 446 | generateTimeout: ViewGenerateTimeoutInSeconds * 1000, 447 | getDecoratedValue: true 448 | } 449 | } 450 | }; 451 | 452 | const replyCachedValue = (h, {value, cached}) => { 453 | const lastModified = cached ? new Date(cached.stored) : new Date(); 454 | return h.response(value).header('Last-Modified', lastModified.toUTCString()); 455 | }; 456 | 457 | init(); 458 | -------------------------------------------------------------------------------- /blockchain.js: -------------------------------------------------------------------------------- 1 | const nearApi = require('near-api-js'); 2 | const api = require('./api'); 3 | const fs = require('fs'); 4 | const fetch = require('node-fetch'); 5 | const {getNetworkFromRpcNode} = require("./api"); 6 | 7 | const settings = JSON.parse(fs.readFileSync(api.CONFIG_PATH, 'utf8')); 8 | 9 | module.exports = { 10 | /** 11 | * @return {string} 12 | */ 13 | GetSignUrl: async function (account_id, method, params, deposit, gas, receiver_id, meta, callback_url, network) { 14 | try { 15 | if(!network) 16 | network = "mainnet"; 17 | const deposit_value = typeof deposit == 'string' ? deposit : nearApi.utils.format.parseNearAmount('' + deposit); 18 | const actions = [method === '!transfer' ? nearApi.transactions.transfer(deposit_value) : nearApi.transactions.functionCall(method, Buffer.from(JSON.stringify(params)), gas, deposit_value)]; 19 | const keypair = nearApi.utils.KeyPair.fromRandom('ed25519'); 20 | const provider = new nearApi.providers.JsonRpcProvider({url: 'https://rpc.' + network + '.near.org'}); 21 | const block = await provider.block({finality: 'final'}); 22 | const txs = [nearApi.transactions.createTransaction(account_id, keypair.publicKey, receiver_id, 1, actions, nearApi.utils.serialize.base_decode(block.header.hash))]; 23 | const newUrl = new URL('sign', 'https://wallet.' + network + '.near.org/'); 24 | newUrl.searchParams.set('transactions', txs.map(transaction => nearApi.utils.serialize.serialize(nearApi.transactions.SCHEMA, transaction)).map(serialized => Buffer.from(serialized).toString('base64')).join(',')); 25 | newUrl.searchParams.set('callbackUrl', callback_url); 26 | if (meta) 27 | newUrl.searchParams.set('meta', meta); 28 | return newUrl.href; 29 | } catch (e) { 30 | return api.reject(e); 31 | } 32 | }, 33 | 34 | /** 35 | * @return {string} 36 | */ 37 | View: async function (recipient, method, params, rpc_node, headers) { 38 | try { 39 | let rpc = rpc_node || settings.rpc_node; 40 | const nearRpc = new nearApi.providers.JsonRpcProvider({url: rpc}); 41 | 42 | const account = new nearApi.Account({ 43 | provider: nearRpc, 44 | networkId: getNetworkFromRpcNode(rpc), 45 | signer: recipient, 46 | headers: (typeof headers !== undefined) ? headers : {} 47 | }, 48 | recipient); 49 | return await account.viewFunction( 50 | recipient, 51 | method, 52 | params 53 | ); 54 | } catch (e) { 55 | return api.reject(e); 56 | } 57 | }, 58 | 59 | Init: async function (master_account_id, master_key, nft_contract, server_host, server_port, rpc_node) { 60 | try { 61 | const new_settings = settings; 62 | if (master_account_id) new_settings.master_account_id = master_account_id; 63 | if (master_key) new_settings.master_key = master_key; 64 | if (nft_contract) new_settings.nft_contract = nft_contract; 65 | if (server_host) new_settings.server_host = server_host; 66 | if (server_port) new_settings.server_port = server_port; 67 | if (rpc_node) new_settings.rpc_node = rpc_node; 68 | 69 | await fs.promises.writeFile(api.CONFIG_PATH, JSON.stringify({ 70 | ...new_settings 71 | })); 72 | 73 | return api.notify("Settings updated."); 74 | } catch (e) { 75 | return api.reject(e); 76 | } 77 | }, 78 | 79 | GetBalance: async function (account_id) { 80 | try { 81 | const body = { 82 | jsonrpc: '2.0', 83 | id: "dontcare", 84 | method: "query", 85 | params: { 86 | request_type: "view_account", 87 | finality: "final", 88 | account_id: account_id 89 | } 90 | }; 91 | 92 | return fetch(settings.rpc_node, { 93 | method: 'post', 94 | body: JSON.stringify(body), 95 | headers: {'Content-Type': 'application/json'} 96 | }) 97 | .then(res => res.json()) 98 | .then(json => { 99 | if (json.error) 100 | return api.reject(json.error.data); 101 | 102 | return json.result.amount 103 | }); 104 | } catch (e) { 105 | return api.reject(e); 106 | } 107 | }, 108 | 109 | DeployContract: async function (account_id, private_key, contract_file) { 110 | try { 111 | const path = `contracts/${contract_file}`; 112 | if (!fs.existsSync(path)) 113 | return api.reject("Contract not found"); 114 | 115 | const account = await this.GetAccountByKey(account_id, private_key); 116 | 117 | const data = [...fs.readFileSync(path)]; 118 | const txs = [nearApi.transactions.deployContract(data)]; 119 | 120 | let res = await account.signAndSendTransaction(account_id, txs); 121 | 122 | if (contract_file === "nft_simple.wasm") 123 | await this.Call(account_id, private_key, 0, "100000000000000", 124 | account_id, "new", {"owner_id": account_id}); 125 | 126 | return res; 127 | } catch (e) { 128 | return api.reject(e); 129 | } 130 | }, 131 | 132 | Call: async function (account_id, private_key, attached_tokens, attached_gas, recipient, method, params, network, rpc_node, headers) { 133 | try { 134 | const account = await this.GetAccountByKey(account_id, private_key, network, rpc_node, headers); 135 | 136 | return await account.functionCall({ 137 | contractId: recipient, 138 | methodName: method, 139 | args: params, 140 | gas: attached_gas, 141 | attachedDeposit: attached_tokens 142 | }); 143 | } catch (e) { 144 | return api.reject(e); 145 | } 146 | }, 147 | 148 | GetMasterAccount: async function () { 149 | try { 150 | const keyPair = nearApi.utils.KeyPair.fromString(settings.master_key); 151 | const keyStore = new nearApi.keyStores.InMemoryKeyStore(); 152 | keyStore.setKey("testnet", settings.master_account_id, keyPair); 153 | 154 | const near = await nearApi.connect({ 155 | networkId: "testnet", 156 | deps: {keyStore}, 157 | masterAccount: settings.master_account_id, 158 | nodeUrl: settings.rpc_node 159 | }); 160 | 161 | return await near.account(settings.master_account_id); 162 | } catch (e) { 163 | return api.reject(e); 164 | } 165 | }, 166 | 167 | GetUserAccount: async function (accountId) { 168 | try { 169 | const user = require('./user'); 170 | 171 | const account_raw = await user.GetAccount(accountId); 172 | const account = JSON.parse(account_raw); 173 | 174 | const keyPair = nearApi.utils.KeyPair.fromString(account.private_key); 175 | const keyStore = new nearApi.keyStores.InMemoryKeyStore(); 176 | keyStore.setKey("testnet", account.account_id, keyPair); 177 | 178 | const near = await nearApi.connect({ 179 | networkId: "testnet", 180 | deps: {keyStore}, 181 | masterAccount: account.account_id, 182 | nodeUrl: settings.rpc_node 183 | }); 184 | 185 | return await near.account(account.account_id); 186 | } catch (e) { 187 | return api.reject(e); 188 | } 189 | }, 190 | 191 | GetAccountByKey: async function (account_id, private_key, network, rpc_node, headers) { 192 | try { 193 | network = network || "testnet"; 194 | rpc_node = rpc_node || settings.rpc_node; 195 | 196 | private_key = private_key.replace('"', ''); 197 | 198 | const keyPair = nearApi.utils.KeyPair.fromString(private_key); 199 | const keyStore = new nearApi.keyStores.InMemoryKeyStore(); 200 | keyStore.setKey(network, account_id, keyPair); 201 | 202 | const near = await nearApi.connect({ 203 | networkId: network, 204 | deps: {keyStore}, 205 | masterAccount: account_id, 206 | nodeUrl: rpc_node, 207 | headers: (typeof headers !== undefined) ? headers : {} 208 | }); 209 | 210 | return await near.account(account_id); 211 | } catch (e) { 212 | return api.reject(e); 213 | } 214 | } 215 | }; 216 | -------------------------------------------------------------------------------- /contracts/nft_simple.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/near-examples/near-api-rest-server/0d3a18e0bf161306223140b19a034bf39c576ead/contracts/nft_simple.wasm -------------------------------------------------------------------------------- /examples/nft_deploy/README.MD: -------------------------------------------------------------------------------- 1 | **INITIALIZE** you NFT contract for multi token types and batch **MINT** each type of token with perpetual royalties. 2 | 3 | Contract used: [NFT-Simple](https://github.com/near-apps/nft-market/tree/main/contracts). 4 | 5 | [Init & mint example](https://explorer.testnet.near.org/accounts/dev-1621541447792-38210652756946). -------------------------------------------------------------------------------- /examples/nft_deploy/deploy_tokens.js: -------------------------------------------------------------------------------- 1 | const {data} = require('./token_types.js'); 2 | const homedir = require("os").homedir(); 3 | const path = require("path"); 4 | const fs = require("fs"); 5 | const fetch = require("node-fetch"); 6 | 7 | const API_SERVER_URL = "http://rest.nearapi.org"; 8 | const CONTRACT_ID = "dev-1621541447792-38210652756946"; 9 | const ACCOUNT_ID = "zavodil.testnet"; 10 | const CREDENTIALS_DIR = ".near-credentials/testnet/"; 11 | 12 | const run = async () => { 13 | const tokens = data.map(({token_type, metadata}, i) => { 14 | return { 15 | token_type, 16 | token_id: token_type + '_1', 17 | metadata: { 18 | ...metadata, 19 | issued_at: Date.now().toString(), 20 | }, 21 | perpetual_royalties: { 22 | ['escrow-' + (i + 1) + '.nft.near']: 1000, //10% 23 | "account-2.near": 500, // 5% 24 | "account-3.near": 100 // 1% 25 | } 26 | } 27 | }); 28 | 29 | // initial add_token_types: 100 tokens of each type 30 | const supply_cap_by_type = tokens.map(({token_type}) => ({ 31 | [token_type]: '100' 32 | })).reduce((a, c) => ({...a, ...c}), {}); 33 | 34 | let tx = await init(CONTRACT_ID, supply_cap_by_type); 35 | console.log(tx); 36 | 37 | // initial mint 1 token of every type 38 | for (let i = 0; i < tokens.length; i++) { 39 | const token = tokens[i]; 40 | tx = await mint(CONTRACT_ID, token); 41 | console.log(tx) 42 | } 43 | return "Finish" 44 | }; 45 | 46 | const init = async function (contract, supply_cap_by_type) { 47 | const body = { 48 | method: 'new', 49 | contract: contract, 50 | 51 | params: { 52 | owner_id: ACCOUNT_ID, 53 | metadata: { 54 | "spec": "nft-1", 55 | "name": "NAME", 56 | "symbol": "NFT" 57 | }, 58 | unlocked: true, 59 | supply_cap_by_type: supply_cap_by_type, 60 | }, 61 | 62 | account_id: ACCOUNT_ID, 63 | private_key: await getPrivateKey(ACCOUNT_ID), 64 | attached_gas: "300000000000000", 65 | attached_tokens: "" 66 | }; 67 | 68 | return await PostResponse("call", body); 69 | }; 70 | 71 | const mint = async function (contract, token) { 72 | const body = { 73 | method: 'nft_mint', 74 | contract: contract, 75 | 76 | params: { 77 | token_id: token.token_id, 78 | metadata: token.metadata, 79 | perpetual_royalties: token.perpetual_royalties, 80 | token_type: token.token_type, 81 | }, 82 | 83 | account_id: ACCOUNT_ID, 84 | private_key: await getPrivateKey(ACCOUNT_ID), 85 | attached_gas: "100000000000000", 86 | attached_tokens: "20000000000000000000000" 87 | }; 88 | 89 | return await PostResponse("call", body); 90 | }; 91 | 92 | const PostResponse = async (operation, body, options) => { 93 | const response = fetch(`${API_SERVER_URL}/${operation}`, { 94 | method: 'POST', 95 | body: JSON.stringify(body), 96 | headers: { 97 | 'Content-type': 'application/json; charset=UTF-8' 98 | } 99 | }) 100 | .then(res => { 101 | return res.text().then(response => { 102 | if (options && options.convertToNear) { 103 | return module.exports.RoundFloat(module.exports.ConvertYoctoNear(response, config.FRACTION_DIGITS)); 104 | } else { 105 | try { 106 | const json = JSON.parse(response); 107 | try { 108 | if (json.error) 109 | return (JSON.parse(json.error)); 110 | else { 111 | return (json); 112 | } 113 | } catch (e) { 114 | throw new Error("PostResponse error for " + operation + " request " + JSON.stringify(body) + ". Error: " + e.message); 115 | } 116 | } catch { 117 | return response; 118 | } 119 | } 120 | }); 121 | 122 | }); 123 | return response; 124 | }; 125 | 126 | const getPrivateKey = async (accountId) => { 127 | const credentialsPath = path.join(homedir, CREDENTIALS_DIR); 128 | const keyPath = credentialsPath + accountId + '.json'; 129 | try { 130 | const credentials = JSON.parse(fs.readFileSync(keyPath)); 131 | return (credentials.private_key); 132 | } catch (e) { 133 | throw new Error("Key not found for account " + keyPath + ". Error: " + e.message); 134 | } 135 | }; 136 | 137 | run().then(r => console.log(r)); -------------------------------------------------------------------------------- /examples/nft_deploy/token_types.js: -------------------------------------------------------------------------------- 1 | const prefix = "test"; 2 | module.exports = { 3 | data: [ 4 | { 5 | token_type: prefix + '_name_1', 6 | metadata: {media: 'hash_1'} 7 | }, 8 | { 9 | token_type: prefix + '_name_2', 10 | metadata: {media: 'hash_2'} 11 | }, 12 | { 13 | token_type: prefix + '_name_3', 14 | metadata: {media: 'hash_3'} 15 | }, 16 | ] 17 | }; -------------------------------------------------------------------------------- /near-api-server.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "server_host": "localhost", 3 | "server_port": 3000, 4 | "rpc_node": "https://rpc.testnet.near.org", 5 | "init_disabled": true 6 | } -------------------------------------------------------------------------------- /near-api-ui/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `yarn start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `yarn test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `yarn build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `yarn eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `yarn build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /near-api-ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "near-api-ui", 3 | "version": "0.1.0", 4 | "license": "UNLICENSED", 5 | "scripts": { 6 | "build": "npm run build:contract && npm run build:web", 7 | "build:contract": "node contract/compile.js", 8 | "build:contract:debug": "node contract/compile.js --debug", 9 | "build:web": "export NODE_ENV=mainnet && rm -r mainnet && parcel build src/index.html --public-url ./ && mv dist mainnet", 10 | "dev:deploy:contract": "near dev-deploy", 11 | "deploy:contract": "near deploy", 12 | "deploy:pages": "gh-pages -d dist/", 13 | "deploy": "npm run build && npm run deploy:contract && npm run deploy:pages", 14 | "prestart": "npm run build:contract:debug && npm run dev:deploy:contract", 15 | "start": "export PORT=39106 && echo The app is starting! It will automatically open in your browser when ready && env-cmd -f ./neardev/dev-account.env parcel src/index.html --open", 16 | "dev": "export PORT=39106 && nodemon --watch contract/src -e rs --exec \"npm run start\"", 17 | "test": "npm run build:contract:debug && cd contract && cargo test -- --nocapture && cd .. && jest test --runInBand", 18 | "build:web:testnet": "export NODE_ENV=testnet && rm -rf testnet && parcel build src/index.html --public-url ./ && mv dist testnet", 19 | "build:contract:testnet": "export NODE_ENV=testnet && node contract/compile.js", 20 | "deploy:contract:testnet": "export NODE_ENV=testnet && near deploy", 21 | "build:web:all": "export NODE_ENV=mainnet && rm -r mainnet && parcel build src/index.html --public-url ./ && mv dist mainnet && export NODE_ENV=testnet && rm -r testnet && parcel build src/index.html --public-url ./ && mv dist testnet", 22 | "dev:clear": "rm -r neardev" 23 | }, 24 | "devDependencies": { 25 | "@babel/core": "~7.12.3", 26 | "@babel/preset-env": "~7.12.1", 27 | "@babel/preset-react": "~7.12.5", 28 | "babel-jest": "~26.6.2", 29 | "bn.js": "^5.1.1", 30 | "env-cmd": "~10.1.0", 31 | "gh-pages": "~3.1.0", 32 | "jest": "~26.6.2", 33 | "jest-environment-node": "~26.6.2", 34 | "near-api-js": "~0.36.2", 35 | "near-cli": "~2.0.0", 36 | "nodemon": "~2.0.3", 37 | "parcel-bundler": "~1.12.4", 38 | "react-file-reader": "~1.1.4", 39 | "react-test-renderer": "~17.0.1", 40 | "react-tooltip": "~4.2.11", 41 | "shelljs": "~0.8.4" 42 | }, 43 | "dependencies": { 44 | "json-url": "^3.0.0", 45 | "query-string": "^4.3.4", 46 | "react": "~17.0.1", 47 | "react-dom": "~17.0.1", 48 | "react-json-view": "~1.21.3", 49 | "regenerator-runtime": "~0.13.5" 50 | }, 51 | "jest": { 52 | "moduleNameMapper": { 53 | "\\.(jpg|ico|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/src/__mocks__/fileMock.js", 54 | "\\.(css|less)$": "/src/__mocks__/fileMock.js" 55 | }, 56 | "setupFiles": [ 57 | "/src/jest.init.js" 58 | ], 59 | "testEnvironment": "near-cli/test_environment", 60 | "testPathIgnorePatterns": [ 61 | "/contract/", 62 | "/node_modules/" 63 | ] 64 | }, 65 | "browserslist": { 66 | "production": [ 67 | ">0.2%", 68 | "not dead", 69 | "not op_mini all" 70 | ], 71 | "development": [ 72 | "last 1 chrome version", 73 | "last 1 firefox version", 74 | "last 1 safari version" 75 | ] 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /near-api-ui/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/near-examples/near-api-rest-server/0d3a18e0bf161306223140b19a034bf39c576ead/near-api-ui/public/favicon.ico -------------------------------------------------------------------------------- /near-api-ui/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /near-api-ui/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/near-examples/near-api-rest-server/0d3a18e0bf161306223140b19a034bf39c576ead/near-api-ui/public/logo192.png -------------------------------------------------------------------------------- /near-api-ui/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/near-examples/near-api-rest-server/0d3a18e0bf161306223140b19a034bf39c576ead/near-api-ui/public/logo512.png -------------------------------------------------------------------------------- /near-api-ui/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /near-api-ui/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /near-api-ui/src/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: BwSeidoRound, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 5 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 6 | sans-serif; 7 | } 8 | 9 | .App { 10 | 11 | } 12 | 13 | .hidden{ 14 | display: none; 15 | } 16 | 17 | .option-buttons{ 18 | padding-top: 10px; 19 | } 20 | 21 | .option-buttons input{ 22 | margin: 0 5px; 23 | } 24 | 25 | .error{ 26 | color: red; 27 | } 28 | 29 | .error, .processed{ 30 | padding-bottom: 10px; 31 | } 32 | 33 | @keyframes App-logo-spin { 34 | from { 35 | transform: rotate(0deg); 36 | } 37 | to { 38 | transform: rotate(360deg); 39 | } 40 | } 41 | 42 | .input-query{ 43 | width: 700px; 44 | font-size: larger; 45 | } 46 | 47 | .query-options{ 48 | display: block; 49 | width: 700px 50 | } 51 | 52 | .button-query{ 53 | font-size: larger; 54 | } 55 | 56 | h1{ 57 | margin: 5px 0; 58 | } 59 | 60 | h1 a, h1 a:hover, h1 a:visited{ 61 | text-decoration: none; 62 | color: #000; 63 | } 64 | 65 | .github-hint{ 66 | padding: 10px 0 0 20px; 67 | color: rgba(0, 0, 0, 0.8); 68 | } 69 | 70 | .json-response{ 71 | display: inline-block; 72 | padding-left: 20px; 73 | } 74 | 75 | @font-face { 76 | font-family: BwSeidoRound; 77 | src: url(./fonts/389947_6_0.eot); 78 | src: url(./fonts/389947_6_0.eot?#iefix) format("embedded-opentype"), url(./fonts/389947_6_0.woff2) format("woff2"), url(./fonts/389947_6_0.woff) format("woff"), url(./fonts/389947_6_0.ttf) format("truetype"); 79 | font-weight: 500; 80 | font-style: normal 81 | } 82 | -------------------------------------------------------------------------------- /near-api-ui/src/App.js: -------------------------------------------------------------------------------- 1 | import 'regenerator-runtime/runtime' 2 | import React, {useState} from 'react'; 3 | import {login, logout} from './utils' 4 | import './App.css'; 5 | import * as nearAPI from 'near-api-js' 6 | import ReactJson from 'react-json-view' 7 | 8 | const codec = require('json-url')('lzw'); 9 | const queryString = require('query-string'); 10 | 11 | import {API_SERVER_URL, MAINNET_RPC} from './config' 12 | 13 | function App() { 14 | const [processing, setProcessing] = useState(false); 15 | const [processedTime, setProcessedTime] = useState(0); 16 | const [errorFlag, setErrorFlag] = useState(false); 17 | const [signedIn, setSignedIn] = useState(false); 18 | const [gasAttached, setGasAttached] = useState("100000000000000"); 19 | const [tokensAttached, setTokensAttached] = useState("1000000000000000000000000"); 20 | const [showCallOptions, setShowCallOptions] = useState(false); 21 | const [request, setRequest] = useState("near view lunanova.pool.f863973.m0 get_accounts '{\"from_index\": 0, \"limit\": 100}'"); 22 | const [response, setResponse] = useState({}); 23 | const [viewNetworkTestnet, setViewNetworkTestnet] = useState(true); 24 | const [viewNetworkDisabled, setViewNetworkDisabled] = useState(false); 25 | 26 | let firstLoad = true; 27 | 28 | const _handleKeyDown = function (e) { 29 | if (e.key === 'Enter') { 30 | _sendForm() 31 | } 32 | } 33 | 34 | const _sendForm = async () => { 35 | try { 36 | 37 | const call = request.toLowerCase().split(/('.*?'|".*?"|\S+)/g); 38 | 39 | if ( 40 | !(call[1] || call[3] || call[5]) || 41 | call[1] !== 'near' || 42 | !['view', 'call'].includes(call[3])) { 43 | setResponse({error: "Illegal command"}); 44 | } else if (call[3] === 'call' && !signedIn) { 45 | setResponse({error: "Sign In to send call requests"}); 46 | } else { 47 | if (!call[9]) 48 | call[9] = "{}"; 49 | 50 | let params; 51 | try { 52 | params = JSON.parse(call[9].replaceAll("'", "")); 53 | } catch (e) { 54 | console.error("Invalid Params"); 55 | params = "{}"; 56 | } 57 | 58 | let body = { 59 | contract: call[5], 60 | method: call[7], 61 | params: params 62 | }; 63 | 64 | if (call[3] === 'call') { 65 | const private_key = window.walletConnection._keyStore.localStorage[`near-api-js:keystore:${window.accountId}:testnet`]; 66 | if (!private_key) { 67 | setResponse({error: "Key wasn't found to sign call request"}); 68 | return; 69 | } 70 | 71 | body = { 72 | ...body, 73 | account_id: window.accountId, 74 | private_key: private_key, 75 | attached_gas: gasAttached, 76 | attached_tokens: tokensAttached, 77 | } 78 | } else { 79 | if (!viewNetworkTestnet) 80 | body = { 81 | ...body, 82 | rpc_node: MAINNET_RPC 83 | } 84 | 85 | SetViewQueryUrl(body); 86 | } 87 | 88 | await GetResponseFromNear(call[3], body); 89 | 90 | } 91 | } catch (err) { 92 | setResponse({error: "Illegal query"}); 93 | console.log(err); 94 | } 95 | } 96 | 97 | const GetResponseFromNear = async (method, body) => { 98 | console.log("GetResponseFromNear") 99 | const t0 = performance.now(); 100 | setProcessing(true); 101 | 102 | return await fetch(`${API_SERVER_URL}/${method}`, { 103 | method: 'POST', 104 | body: JSON.stringify(body), 105 | headers: { 106 | 'Content-type': 'application/json; charset=UTF-8' 107 | } 108 | }) 109 | .then(res => res.json()) 110 | .then(res => { 111 | if (res.error) 112 | setResponse(JSON.parse(res.error)); 113 | else 114 | setResponse(res); 115 | 116 | setProcessing(false) 117 | setErrorFlag(!!res.error); 118 | setProcessedTime(Math.round((performance.now() - t0) / 10) / 100); 119 | }) 120 | }; 121 | 122 | React.useEffect( 123 | async () => { 124 | if (window.walletConnection.isSignedIn()) { 125 | setSignedIn(true) 126 | } 127 | 128 | if (firstLoad && location.search) { 129 | const query = JSON.parse(JSON.stringify(queryString.parse(location.search))); 130 | if (query && query.hasOwnProperty("q")) { 131 | codec.decompress(query.q).then(async (json) => { 132 | console.log("Loading url query..."); 133 | console.log(json); 134 | console.log(`near view ${json.contract} ${json.method} '${JSON.stringify(json.params)}'`); 135 | setRequest(`near view ${json.contract} ${json.method} '${JSON.stringify(json.params)}'`); 136 | setViewNetworkTestnet(json.rpc_node !== MAINNET_RPC); 137 | await GetResponseFromNear("view", json); 138 | }); 139 | } 140 | } 141 | 142 | firstLoad = false; 143 | }, 144 | [] 145 | ); 146 | 147 | React.useEffect(() => { 148 | const timeOutId = setTimeout(() => UpdateQuery(request), 500); 149 | return () => clearTimeout(timeOutId); 150 | }, [request]); 151 | 152 | const SetViewQueryUrl = (request) => { 153 | codec.compress(request).then(compressed_string => { 154 | const url = location.protocol + '//' + location.host + location.pathname + '?q=' + compressed_string; 155 | window.history.replaceState({}, document.title, url); 156 | console.log(url) 157 | }); 158 | } 159 | 160 | const SignButton = () => { 161 | return (signedIn ? 162 |
{window.accountId}  163 | 164 |
165 | : 166 | 167 | ) 168 | } 169 | 170 | const JsonOutput = () => { 171 | if(!response || (IsObject(response) && !Object.keys(response).length)) 172 | return null; 173 | 174 | return IsObject(response) 175 | ? 176 | :
{response}
; 177 | }; 178 | 179 | const IsObject = (obj) => { 180 | return obj !== undefined && obj !== null && typeof obj == 'object'; 181 | } 182 | 183 | const UpdateQuery = (query) => { 184 | query = query.toLowerCase(); 185 | const isCall = query.startsWith("near call"); 186 | setShowCallOptions(isCall); 187 | setViewNetworkDisabled(isCall); 188 | if (isCall) 189 | setViewNetworkTestnet(true); 190 | }; 191 | 192 | return ( 193 |
194 | 195 |
196 | 197 |
198 | 199 |
200 |
201 |
202 |

NEAR REST API Web

203 |
204 |
205 |
Query
206 |
Network: 207 | 208 | { 211 | setViewNetworkTestnet(e.target.checked) 212 | }}/> 213 | Testnet 214 |
215 |
216 | 217 |
218 | setRequest(e.target.value)} 221 | /> 222 | 225 |
226 |
227 | 228 |
229 | Gas Attached 230 | setGasAttached(e.target.value)} 232 | /> 233 | 234 | Tokens Attached 235 | setTokensAttached(e.target.value)} 237 | /> 238 |
239 |
240 | 241 | 242 |
243 | {errorFlag 244 | ?
ERROR!
245 | : (processedTime 246 | ?
Process time: {processedTime} seconds
247 | : null) 248 | } 249 | 250 |
251 |
252 | Interact with the NEAR blockchain using a simple REST API. 253 | Github 254 |
255 | 256 | 257 |
258 | ); 259 | } 260 | 261 | export default App; 262 | -------------------------------------------------------------------------------- /near-api-ui/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /near-api-ui/src/assets/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/near-examples/near-api-rest-server/0d3a18e0bf161306223140b19a034bf39c576ead/near-api-ui/src/assets/android-chrome-192x192.png -------------------------------------------------------------------------------- /near-api-ui/src/assets/android-chrome-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/near-examples/near-api-rest-server/0d3a18e0bf161306223140b19a034bf39c576ead/near-api-ui/src/assets/android-chrome-384x384.png -------------------------------------------------------------------------------- /near-api-ui/src/assets/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/near-examples/near-api-rest-server/0d3a18e0bf161306223140b19a034bf39c576ead/near-api-ui/src/assets/apple-touch-icon.png -------------------------------------------------------------------------------- /near-api-ui/src/assets/explorer-bg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /near-api-ui/src/assets/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/near-examples/near-api-rest-server/0d3a18e0bf161306223140b19a034bf39c576ead/near-api-ui/src/assets/favicon-16x16.png -------------------------------------------------------------------------------- /near-api-ui/src/assets/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/near-examples/near-api-rest-server/0d3a18e0bf161306223140b19a034bf39c576ead/near-api-ui/src/assets/favicon-32x32.png -------------------------------------------------------------------------------- /near-api-ui/src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/near-examples/near-api-rest-server/0d3a18e0bf161306223140b19a034bf39c576ead/near-api-ui/src/assets/favicon.ico -------------------------------------------------------------------------------- /near-api-ui/src/assets/icon-network-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /near-api-ui/src/assets/logo-black.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /near-api-ui/src/assets/logo-white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /near-api-ui/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /near-api-ui/src/assets/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/near-examples/near-api-rest-server/0d3a18e0bf161306223140b19a034bf39c576ead/near-api-ui/src/assets/mstile-150x150.png -------------------------------------------------------------------------------- /near-api-ui/src/assets/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/assets/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/assets/android-chrome-384x384.png", 12 | "sizes": "384x384", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /near-api-ui/src/config.js: -------------------------------------------------------------------------------- 1 | const CONTRACT_NAME = process.env.CONTRACT_NAME || "null_address.testnet"; 2 | 3 | module.exports = { 4 | API_SERVER_URL: "http://rest.nearspace.info", 5 | MAINNET_RPC: "https://rpc.mainnet.near.org", 6 | getConfig: (env) => { 7 | switch (env) { 8 | 9 | case 'production': 10 | case 'mainnet': 11 | return { 12 | networkId: 'mainnet', 13 | nodeUrl: 'https://rpc.mainnet.near.org', 14 | contractName: CONTRACT_NAME || "null_address.near", 15 | walletUrl: 'https://wallet.near.org', 16 | helperUrl: 'https://helper.mainnet.near.org', 17 | explorerUrl: 'https://explorer.mainnet.near.org', 18 | } 19 | case 'development': 20 | case 'testnet': 21 | return { 22 | networkId: 'testnet', 23 | nodeUrl: 'https://rpc.testnet.near.org', 24 | contractName: CONTRACT_NAME || "null_address.testnet", 25 | walletUrl: 'https://wallet.testnet.near.org', 26 | helperUrl: 'https://helper.testnet.near.org', 27 | explorerUrl: 'https://explorer.testnet.near.org', 28 | } 29 | case 'betanet': 30 | return { 31 | networkId: 'betanet', 32 | nodeUrl: 'https://rpc.betanet.near.org', 33 | contractName: CONTRACT_NAME, 34 | walletUrl: 'https://wallet.betanet.near.org', 35 | helperUrl: 'https://helper.betanet.near.org', 36 | explorerUrl: 'https://explorer.betanet.near.org', 37 | } 38 | case 'local': 39 | return { 40 | networkId: 'local', 41 | nodeUrl: 'http://localhost:3030', 42 | keyPath: `${process.env.HOME}/.near/validator_key.json`, 43 | walletUrl: 'http://localhost:4000/wallet', 44 | contractName: CONTRACT_NAME, 45 | } 46 | case 'test': 47 | case 'ci': 48 | return { 49 | networkId: 'shared-test', 50 | nodeUrl: 'https://rpc.ci-testnet.near.org', 51 | contractName: CONTRACT_NAME, 52 | masterAccount: 'test.near', 53 | } 54 | case 'ci-betanet': 55 | return { 56 | networkId: 'shared-test-staging', 57 | nodeUrl: 'https://rpc.ci-betanet.near.org', 58 | contractName: CONTRACT_NAME, 59 | masterAccount: 'test.near', 60 | } 61 | default: 62 | throw Error(`Unconfigured environment '${env}'. Can be configured in src/config.js.`) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /near-api-ui/src/fonts/389947_6_0.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/near-examples/near-api-rest-server/0d3a18e0bf161306223140b19a034bf39c576ead/near-api-ui/src/fonts/389947_6_0.eot -------------------------------------------------------------------------------- /near-api-ui/src/fonts/389947_6_0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/near-examples/near-api-rest-server/0d3a18e0bf161306223140b19a034bf39c576ead/near-api-ui/src/fonts/389947_6_0.ttf -------------------------------------------------------------------------------- /near-api-ui/src/fonts/389947_6_0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/near-examples/near-api-rest-server/0d3a18e0bf161306223140b19a034bf39c576ead/near-api-ui/src/fonts/389947_6_0.woff -------------------------------------------------------------------------------- /near-api-ui/src/fonts/389947_6_0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/near-examples/near-api-rest-server/0d3a18e0bf161306223140b19a034bf39c576ead/near-api-ui/src/fonts/389947_6_0.woff2 -------------------------------------------------------------------------------- /near-api-ui/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /near-api-ui/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | NEAR REST API 13 | 14 | 15 | 16 |
17 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /near-api-ui/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | import { initContract } from './utils' 5 | 6 | window.nearInitPromise = initContract() 7 | .then(() => { 8 | ReactDOM.render( 9 | , 10 | document.querySelector('#root') 11 | ) 12 | }) 13 | .catch(console.error) 14 | -------------------------------------------------------------------------------- /near-api-ui/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /near-api-ui/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /near-api-ui/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /near-api-ui/src/utils.js: -------------------------------------------------------------------------------- 1 | import {connect, Contract, keyStores, WalletConnection} from 'near-api-js' 2 | import {getConfig} from './config' 3 | 4 | const nearConfig = getConfig(process.env.NODE_ENV || 'development') 5 | 6 | // Initialize contract & set global variables 7 | export async function initContract() { 8 | // Initialize connection to the NEAR testnet 9 | const near = await connect(Object.assign({deps: {keyStore: new keyStores.BrowserLocalStorageKeyStore()}}, nearConfig)) 10 | 11 | // Initializing Wallet based Account. It can work with NEAR testnet wallet that 12 | // is hosted at https://wallet.testnet.near.org 13 | window.walletConnection = new WalletConnection(near) 14 | 15 | // Getting the Account ID. If still unauthorized, it's just empty string 16 | window.accountId = window.walletConnection.getAccountId() 17 | 18 | // Initializing our contract APIs by contract name and configuration 19 | window.contract = await new Contract(window.walletConnection.account(), nearConfig.contractName, { 20 | // View methods are read only. They don't modify the state, but usually return some value. 21 | viewMethods: ['get_deposit'], 22 | // Change methods can modify the state. But you don't receive the returned value when called. 23 | changeMethods: ['deposit', 'multisend_from_balance', 'multisend_attached_tokens'], 24 | }) 25 | } 26 | 27 | export function logout() { 28 | window.walletConnection.signOut() 29 | // reload page 30 | window.location.replace(window.location.origin + window.location.pathname) 31 | } 32 | 33 | export function login() { 34 | // Allow the current app to make calls to the specified contract on the 35 | // user's behalf. 36 | // This works by creating a new access key for the user's account and storing 37 | // the private key in localStorage. 38 | window.walletConnection.requestSignIn("", "NEAR REST API") 39 | } 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "near-api-server", 3 | "version": "1.1.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Vadim Ilin", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@hapi/catbox-memory": "^5.0.1", 13 | "@hapi/hapi": "^18.1.0", 14 | "body-parser": "~1.19.0", 15 | "faker": "~5.4.0", 16 | "fs": "*", 17 | "near-api-js": "~0.44.1", 18 | "near-cli": "^2.2.0", 19 | "near-seed-phrase": "^0.1.0", 20 | "node-fetch": "^2.6.1", 21 | "path": "^0.12.7", 22 | "pg": "^8.6.0" 23 | }, 24 | "bin": { 25 | "near-api-server": "./app.js" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /token.js: -------------------------------------------------------------------------------- 1 | const blockchain = require('./blockchain'); 2 | const api = require('./api'); 3 | 4 | const fs = require('fs'); 5 | const settings = JSON.parse(fs.readFileSync(api.CONFIG_PATH, 'utf8')); 6 | 7 | module.exports = { 8 | 9 | /** 10 | * @return {string} 11 | */ 12 | ViewNFT: async function (tokenId, contract) { 13 | try { 14 | const nftContract = contract ? contract : settings.nft_contract; 15 | return await blockchain.View( 16 | nftContract, 17 | "nft_token", 18 | {token_id: tokenId} 19 | ); 20 | } catch (e) { 21 | return api.reject(e); 22 | } 23 | }, 24 | 25 | /** 26 | * @return {string} 27 | */ 28 | MintNFT: async function (tokenId, metadata, contractAccountId, account_id, private_key) { 29 | const nftContract = contractAccountId ? contractAccountId : settings.nft_contract; 30 | 31 | let account = !(account_id && private_key) 32 | ? await blockchain.GetMasterAccount() 33 | : await blockchain.GetAccountByKey(account_id, private_key); 34 | 35 | try { 36 | const tx = await account.functionCall( 37 | nftContract, 38 | "nft_mint", 39 | { 40 | "token_id": tokenId, 41 | "metadata": metadata 42 | }, 43 | '100000000000000', 44 | '10000000000000000000000'); 45 | 46 | if (!tx.status.Failure) 47 | return tx.transaction.hash 48 | } catch (e) { 49 | return api.reject(e); 50 | } 51 | }, 52 | 53 | TransferNFT: async function (tokenId, receiverId, enforceOwnerId, memo, contractAccountId, owner_private_key) { 54 | try { 55 | const nftContract = contractAccountId ? contractAccountId : settings.nft_contract; 56 | let account; 57 | 58 | account = !(enforceOwnerId && owner_private_key) 59 | ? ((enforceOwnerId === settings.master_account_id) 60 | ? await blockchain.GetMasterAccount() 61 | : await blockchain.GetUserAccount(enforceOwnerId)) 62 | : await blockchain.GetAccountByKey(enforceOwnerId, owner_private_key); 63 | 64 | return await account.functionCall( 65 | nftContract, 66 | "nft_transfer", 67 | { 68 | "token_id": tokenId, 69 | "receiver_id": receiverId, 70 | "enforce_owner_id": enforceOwnerId, 71 | "memo": memo 72 | }, 73 | '100000000000000', 74 | '1'); 75 | } catch (e) { 76 | return api.reject(e); 77 | } 78 | } 79 | }; 80 | -------------------------------------------------------------------------------- /user.js: -------------------------------------------------------------------------------- 1 | const nearApi = require('near-api-js'); 2 | const blockchain = require('./blockchain'); 3 | const nearSeedPhrase = require('near-seed-phrase'); 4 | const fs = require('fs'); 5 | 6 | const storageFolder = "storage"; 7 | 8 | module.exports = { 9 | GenerateKeyPair: async function () { 10 | const keypair = nearApi.utils.KeyPair.fromRandom('ed25519'); 11 | 12 | return { 13 | public_key: keypair.publicKey.toString(), 14 | private_key: keypair.secretKey 15 | }; 16 | }, 17 | 18 | CreateKeyPair: async function (name) { 19 | const keypair = nearApi.utils.KeyPair.fromRandom('ed25519'); 20 | 21 | const account = 22 | { 23 | account_id: name, 24 | public_key: keypair.publicKey.toString(), 25 | private_key: keypair.secretKey 26 | }; 27 | 28 | return account; 29 | }, 30 | 31 | /** 32 | * @return {string} 33 | */ 34 | GetFileName: function (account_id) { 35 | return `${storageFolder}/${account_id}.json`; 36 | }, 37 | 38 | SaveKeyPair: async function (account) { 39 | if (!fs.existsSync(storageFolder)) 40 | fs.mkdirSync(storageFolder); 41 | 42 | const filename = this.GetFileName(account.account_id); 43 | account.private_key = "ed25519:" + account.private_key; 44 | 45 | await fs.promises.writeFile(filename, JSON.stringify(account)); 46 | }, 47 | 48 | /** 49 | * @return {boolean} 50 | */ 51 | CreateAccount: async function (new_account) { 52 | const account = await blockchain.GetMasterAccount(); 53 | 54 | const res = await account.createAccount(new_account.account_id, new_account.public_key, '200000000000000000000000'); 55 | 56 | try { 57 | if (res['status'].hasOwnProperty('SuccessValue')) { 58 | await this.SaveKeyPair(new_account); 59 | return true 60 | } 61 | } catch (e) { 62 | console.log(e); 63 | } 64 | return false; 65 | }, 66 | 67 | GetAccount: async function (account_id) { 68 | const filename = this.GetFileName(account_id); 69 | return await fs.promises.readFile(filename, 'utf8'); 70 | }, 71 | 72 | GetKeysFromSeedPhrase: async function (seedPhrase) { 73 | return nearSeedPhrase.parseSeedPhrase(seedPhrase); 74 | } 75 | }; 76 | 77 | 78 | --------------------------------------------------------------------------------