From df853df06a3c9e91c16132e7c8621d68ffd20099 Mon Sep 17 00:00:00 2001 From: rimatik Date: Wed, 20 Dec 2023 15:56:53 +0100 Subject: [PATCH] added nearProviderService with buy_for_user method --- package.json | 2 + src/common/config/configuration.ts | 6 + .../near-provider/near-provider.service.ts | 120 ++++++++++++++++++ yarn.lock | 40 +++++- 4 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 src/modules/near-provider/near-provider.service.ts diff --git a/package.json b/package.json index bb9e55f..ed075b4 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@types/uuid": "^9.0.2", "aws-sdk": "^2.1413.0", "axios": "^1.4.0", + "bn.js": "^5.2.1", "borsh": "^0.7.0", "cache-manager": "^5.2.3", "class-transformer": "^0.5.1", @@ -65,6 +66,7 @@ "@nestjs/cli": "^9.0.0", "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", + "@types/bn.js": "^5.1.5", "@types/cron": "^2.0.1", "@types/express": "^4.17.13", "@types/jest": "29.2.4", diff --git a/src/common/config/configuration.ts b/src/common/config/configuration.ts index cc48b6e..f0f9e00 100644 --- a/src/common/config/configuration.ts +++ b/src/common/config/configuration.ts @@ -46,4 +46,10 @@ export const configuration = () => ({ }, email_domains: process.env.EMAIL_DOMAINS, storage_cost_usd: process.env.STORAGE_COST_USD, + near: { + network_id: process.env.NEAR_NETWORK_ID, + nft_contract_account_id: process.env.NEAR_NFT_CONTRACT_ACCOUNT_ID, + master_account_id: process.env.NEAR_MASTER_ACCOUNT_ID, + master_account_private_key: process.env.NEAR_MASTER_ACCOUNT_PRIVATE_KEY, + }, }); diff --git a/src/modules/near-provider/near-provider.service.ts b/src/modules/near-provider/near-provider.service.ts new file mode 100644 index 0000000..2fed707 --- /dev/null +++ b/src/modules/near-provider/near-provider.service.ts @@ -0,0 +1,120 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { BN } from 'bn.js'; +import * as nearAPI from 'near-api-js'; + +/* It's a service that connects to the NEAR blockchain and provides methods for interacting with the NFT contract */ +@Injectable() +export class NearProviderService { + private readonly logger = new Logger(NearProviderService.name); + + // NEAR network ID (e.g. localnet, betanet, testnet, mainnet). For us it's testnet or mainnet + private networkId: string; + + // NEAR account ID of the contract that will have NFTs + private nftContractAccountId: string; + + // NEAR account ID of the account that will pay to mint NFTs + private masterAccountId: string; + + // NEAR account private key of the account that will pay to mint NFTs + private masterAccountPrivateKey: string; + + // NEAR config + private config: nearAPI.ConnectConfig; + + // NEAR instance + private near: nearAPI.Near | null; + + // NEAR account instance of the master account that will call contract to mint NFTs + private masterAccount: nearAPI.Account | null; + + // NEAR key store + private keyStore = new nearAPI.keyStores.InMemoryKeyStore(); + + // Boatload of gas to pay for minting/burning NFTs (300 Tgas). We don't care about this gas because NEAR will refund unused gas + private gas = new BN('300000000000000'); + + /** + * We're creating a constructor function that takes in a configService object as a parameter using dependecy injection. We're + * then setting the networkId, nftContractAccountId, masterAccountId, and masterAccountPrivateKey to + * the values that we get from the configService object + * @param {ConfigService} configService - This is a service that we'll use to get the configuration + * values from the .env file. + */ + constructor(private readonly configService: ConfigService) { + this.networkId = this.configService.get('near.network_id'); + this.nftContractAccountId = this.configService.get( + 'near.nft_contract_account_id', + ); + this.masterAccountId = this.configService.get('near.master_account_id'); + this.masterAccountPrivateKey = this.configService.get( + 'near.master_account_private_key', + ); + + this.near = null; + this.masterAccount = null; + } + + async onModuleInit() { + await this.init(); + } + + /** + * It connects to the NEAR blockchain and sets up the master account + */ + private async init() { + if (this.near && this.masterAccount) return; + + this.config = { + networkId: this.networkId, + nodeUrl: `https://rpc.${this.networkId}.near.org`, + walletUrl: `https://wallet.${this.networkId}.near.org`, + helperUrl: `https://helper.${this.networkId}.near.org`, + }; + + const masterAccountKeyPair = nearAPI.KeyPair.fromString( + this.masterAccountPrivateKey, + ); + + await this.keyStore.setKey( + this.config.networkId, + this.masterAccountId, + masterAccountKeyPair, + ); + + this.near = await nearAPI.connect({ + ...this.config, + keyStore: this.keyStore, + }); + + this.masterAccount = await this.near.account(this.masterAccountId); + } + + /** + * It buys NFT by tokenId for given accountId + * @param {string} tokenId - The token ID of the NFT you want to buy. + * @param {string} accountId - The account ID of the account that wants to buy. + * @returns A boolean value. + */ + async buyForUser(tokenId: string, accountId: string): Promise { + try { + await this.masterAccount.functionCall({ + contractId: this.nftContractAccountId, + methodName: 'buy_for_user', + args: { + tokenId: tokenId, + accountId: accountId, + }, + gas: this.gas, + attachedDeposit: '0' as any, + }); + + return true; + } catch (error) { + this.logger.error('Error executing contract function call: ', error); + } + + return false; + } +} diff --git a/yarn.lock b/yarn.lock index 7233021..d04bba6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1565,6 +1565,15 @@ __metadata: languageName: node linkType: hard +"@types/bn.js@npm:^5.1.5": + version: 5.1.5 + resolution: "@types/bn.js@npm:5.1.5" + dependencies: + "@types/node": "*" + checksum: c87b28c4af74545624f8a3dae5294b16aa190c222626e8d4b2e327b33b1a3f1eeb43e7a24d914a9774bca43d8cd6e1cb0325c1f4b3a244af6693a024e1d918e6 + languageName: node + linkType: hard + "@types/body-parser@npm:*": version: 1.19.2 resolution: "@types/body-parser@npm:1.19.2" @@ -1763,6 +1772,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:>=8.1.0": + version: 20.10.5 + resolution: "@types/node@npm:20.10.5" + dependencies: + undici-types: ~5.26.4 + checksum: e216b679f545a8356960ce985a0e53c3a58fff0eacd855e180b9e223b8db2b5bd07b744a002b8c1f0c37f9194648ab4578533b5c12df2ec10cc02f61d20948d2 + languageName: node + linkType: hard + "@types/oauth@npm:*": version: 0.9.1 resolution: "@types/oauth@npm:0.9.1" @@ -2671,7 +2689,7 @@ __metadata: languageName: node linkType: hard -"bn.js@npm:5.2.1, bn.js@npm:^5.2.0": +"bn.js@npm:5.2.1, bn.js@npm:^5.2.0, bn.js@npm:^5.2.1": version: 5.2.1 resolution: "bn.js@npm:5.2.1" checksum: 3dd8c8d38055fedfa95c1d5fc3c99f8dd547b36287b37768db0abab3c239711f88ff58d18d155dd8ad902b0b0cee973747b7ae20ea12a09473272b0201c9edd3 @@ -7122,6 +7140,7 @@ __metadata: "@nestjs/testing": ^9.0.0 "@nestjs/typeorm": ^10.0.0 "@sendgrid/mail": ^7.7.0 + "@types/bn.js": ^5.1.5 "@types/cron": ^2.0.1 "@types/express": ^4.17.13 "@types/jest": 29.2.4 @@ -7134,6 +7153,7 @@ __metadata: "@typescript-eslint/parser": ^5.0.0 aws-sdk: ^2.1413.0 axios: ^1.4.0 + bn.js: ^5.2.1 borsh: ^0.7.0 cache-manager: ^5.2.3 class-transformer: ^0.5.1 @@ -7155,6 +7175,7 @@ __metadata: reflect-metadata: ^0.1.13 rxjs: ^7.2.0 source-map-support: ^0.5.20 + stripe: ^14.9.0 supertest: ^6.1.3 ts-jest: 29.0.3 ts-loader: ^9.2.3 @@ -7826,6 +7847,16 @@ __metadata: languageName: node linkType: hard +"stripe@npm:^14.9.0": + version: 14.9.0 + resolution: "stripe@npm:14.9.0" + dependencies: + "@types/node": ">=8.1.0" + qs: ^6.11.0 + checksum: 3d7a9b8c70adcaeee5d37a4ad02310b3b16872ad68fe59f28a035d15d0514d6cef0a64bdd62245b4379772a83114a9abc50a5397948cef9a5c54388a62b0aaa6 + languageName: node + linkType: hard + "superagent@npm:^8.0.5": version: 8.0.9 resolution: "superagent@npm:8.0.9" @@ -8392,6 +8423,13 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~5.26.4": + version: 5.26.5 + resolution: "undici-types@npm:5.26.5" + checksum: 3192ef6f3fd5df652f2dc1cd782b49d6ff14dc98e5dced492aa8a8c65425227da5da6aafe22523c67f035a272c599bb89cfe803c1db6311e44bed3042fc25487 + languageName: node + linkType: hard + "unique-filename@npm:^3.0.0": version: 3.0.0 resolution: "unique-filename@npm:3.0.0"