mirror of
https://github.com/oceanprotocol/ocean.js.git
synced 2024-11-26 20:39:05 +01:00
Merge pull request #36 from oceanprotocol/feature/alex_datatokens_class
Feature: datatokens class
This commit is contained in:
commit
19ef201bd6
18
package-lock.json
generated
18
package-lock.json
generated
@ -556,19 +556,6 @@
|
|||||||
"fastq": "^1.6.0"
|
"fastq": "^1.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@oceanprotocol/keeper-contracts": {
|
|
||||||
"version": "0.13.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@oceanprotocol/keeper-contracts/-/keeper-contracts-0.13.2.tgz",
|
|
||||||
"integrity": "sha512-915dcnzCHEuvsmRKqVj0RxHT3T386lSJh8WREe4dsnrXHsi1ULNYxX0Ts/cvalv6bRL+aqyaZ6gN3l3nkfwEDg=="
|
|
||||||
},
|
|
||||||
"@oceanprotocol/secret-store-client": {
|
|
||||||
"version": "0.0.15",
|
|
||||||
"resolved": "https://registry.npmjs.org/@oceanprotocol/secret-store-client/-/secret-store-client-0.0.15.tgz",
|
|
||||||
"integrity": "sha512-5yNNA+qdjut9/nMiFKJd4D4io+GhzdfvdqVd5YMkgT9RV1qDosGj6NVsKArYay6g+tQH7pCJ6Y1FiAbhaaFB9g==",
|
|
||||||
"requires": {
|
|
||||||
"node-fetch": "^2.6.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@octokit/auth-token": {
|
"@octokit/auth-token": {
|
||||||
"version": "2.4.1",
|
"version": "2.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.1.tgz",
|
||||||
@ -6771,6 +6758,11 @@
|
|||||||
"integrity": "sha512-33X7H/wdfO99GdRLLgkjUrD4geAFdq/Uv0kl3HD4da6HDixd2GUg8Mw7dahLCV9r/EARkmtYBB6Tch4EEokFTQ==",
|
"integrity": "sha512-33X7H/wdfO99GdRLLgkjUrD4geAFdq/Uv0kl3HD4da6HDixd2GUg8Mw7dahLCV9r/EARkmtYBB6Tch4EEokFTQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"fs": {
|
||||||
|
"version": "0.0.1-security",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz",
|
||||||
|
"integrity": "sha1-invTcYa23d84E/I4WLV+yq9eQdQ="
|
||||||
|
},
|
||||||
"fs-constants": {
|
"fs-constants": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
"@ethereum-navigator/navigator": "^0.5.0",
|
"@ethereum-navigator/navigator": "^0.5.0",
|
||||||
"bignumber.js": "^9.0.0",
|
"bignumber.js": "^9.0.0",
|
||||||
"deprecated-decorator": "^0.1.6",
|
"deprecated-decorator": "^0.1.6",
|
||||||
|
"fs": "0.0.1-security",
|
||||||
"node-fetch": "^2.6.0",
|
"node-fetch": "^2.6.0",
|
||||||
"save-file": "^2.3.1",
|
"save-file": "^2.3.1",
|
||||||
"uuid": "^8.0.0",
|
"uuid": "^8.0.0",
|
||||||
|
@ -2,8 +2,6 @@ import Web3 from 'web3'
|
|||||||
import Config from './models/Config'
|
import Config from './models/Config'
|
||||||
import { Logger, LoggerInstance, LogLevel } from './utils'
|
import { Logger, LoggerInstance, LogLevel } from './utils'
|
||||||
import { Ocean } from './ocean/Ocean'
|
import { Ocean } from './ocean/Ocean'
|
||||||
import { OceanFactoryABI } from './datatokens/FactoryABI'
|
|
||||||
import { OceanDataTokenABI } from './datatokens/DatatokensABI'
|
|
||||||
|
|
||||||
export interface InstantiableConfig {
|
export interface InstantiableConfig {
|
||||||
ocean: Ocean
|
ocean: Ocean
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import Account from '../ocean/Account'
|
import Account from '../ocean/Account'
|
||||||
|
|
||||||
|
const defaultFactoryABI = require('../datatokens/FactoryABI.json')
|
||||||
|
const defaultDatatokensABI = require('../datatokens/DatatokensABI.json')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a interface to DataTokens
|
* Provides a interface to DataTokens
|
||||||
|
|
||||||
@ -25,8 +28,9 @@ export class DataTokens {
|
|||||||
web3: any
|
web3: any
|
||||||
) {
|
) {
|
||||||
this.factoryAddress = factoryAddress
|
this.factoryAddress = factoryAddress
|
||||||
this.factoryABI = factoryABI
|
|
||||||
this.datatokensABI = datatokensABI
|
this.factoryABI = factoryABI || defaultFactoryABI
|
||||||
|
this.datatokensABI = datatokensABI || defaultDatatokensABI
|
||||||
this.web3 = web3
|
this.web3 = web3
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,11 +38,37 @@ export class DataTokens {
|
|||||||
* Create new datatoken
|
* Create new datatoken
|
||||||
* @param {String} metaDataStoreURI
|
* @param {String} metaDataStoreURI
|
||||||
* @param {Account} account
|
* @param {Account} account
|
||||||
|
* @param {String} name
|
||||||
|
* @param {String} symbol
|
||||||
|
* @param {Number} cap
|
||||||
* @return {Promise<string>} datatoken address
|
* @return {Promise<string>} datatoken address
|
||||||
*/
|
*/
|
||||||
public async create(metaDataStoreURI: string, account: Account): Promise<string> {
|
public async create(
|
||||||
// TO DO
|
metaDataStoreURI: string,
|
||||||
return ''
|
account: Account,
|
||||||
|
name?: string,
|
||||||
|
symbol?: string,
|
||||||
|
cap?: number
|
||||||
|
): Promise<string> {
|
||||||
|
// TODO - Autogenerate name, symbol & cap if missing
|
||||||
|
if (!name) name = 'DTTest1'
|
||||||
|
if (!symbol) symbol = 'DT1'
|
||||||
|
if (!cap) cap = 1000000
|
||||||
|
// Create factory contract object
|
||||||
|
const factory = new this.web3.eth.Contract(this.factoryABI, this.factoryAddress, {
|
||||||
|
from: account.getId()
|
||||||
|
})
|
||||||
|
// Invoke createToken function of the contract
|
||||||
|
const trxReceipt = await factory.methods
|
||||||
|
.createToken(name, symbol, cap, metaDataStoreURI, account.getId())
|
||||||
|
.send()
|
||||||
|
let tokenAddress = null
|
||||||
|
try {
|
||||||
|
tokenAddress = trxReceipt.events.TokenCreated.returnValues[0]
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
return tokenAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -51,12 +81,17 @@ export class DataTokens {
|
|||||||
*/
|
*/
|
||||||
public async approve(
|
public async approve(
|
||||||
dataTokenAddress: string,
|
dataTokenAddress: string,
|
||||||
toAddress: string,
|
spender: string,
|
||||||
amount: number,
|
amount: number,
|
||||||
account: Account
|
account: Account
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// TO DO
|
const datatoken = new this.web3.eth.Contract(
|
||||||
return ''
|
this.datatokensABI,
|
||||||
|
dataTokenAddress,
|
||||||
|
{ from: account.getId() }
|
||||||
|
)
|
||||||
|
const trxReceipt = await datatoken.methods.approve(spender, amount).send()
|
||||||
|
return trxReceipt
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -93,8 +128,14 @@ export class DataTokens {
|
|||||||
amount: number,
|
amount: number,
|
||||||
toAddress?: string
|
toAddress?: string
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// TO DO
|
const address = toAddress || account.getId()
|
||||||
return ''
|
const datatoken = new this.web3.eth.Contract(
|
||||||
|
this.datatokensABI,
|
||||||
|
dataTokenAddress,
|
||||||
|
{ from: account.getId() }
|
||||||
|
)
|
||||||
|
const trxReceipt = await datatoken.methods.mint(address, amount).send()
|
||||||
|
return trxReceipt
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -111,8 +152,13 @@ export class DataTokens {
|
|||||||
amount: number,
|
amount: number,
|
||||||
account: Account
|
account: Account
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// TO DO
|
const datatoken = new this.web3.eth.Contract(
|
||||||
return ''
|
this.datatokensABI,
|
||||||
|
dataTokenAddress,
|
||||||
|
{ from: account.getId() }
|
||||||
|
)
|
||||||
|
const trxReceipt = await datatoken.methods.transfer(toAddress, amount).send()
|
||||||
|
return trxReceipt
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -129,8 +175,15 @@ export class DataTokens {
|
|||||||
amount: number,
|
amount: number,
|
||||||
account: Account
|
account: Account
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// TO DO
|
const datatoken = new this.web3.eth.Contract(
|
||||||
return ''
|
this.datatokensABI,
|
||||||
|
dataTokenAddress,
|
||||||
|
{ from: account.getId() }
|
||||||
|
)
|
||||||
|
const trxReceipt = await datatoken.methods
|
||||||
|
.transferFrom(fromAddress, account.getId(), amount)
|
||||||
|
.send()
|
||||||
|
return trxReceipt
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -140,7 +193,72 @@ export class DataTokens {
|
|||||||
* @return {Promise<number>} balance
|
* @return {Promise<number>} balance
|
||||||
*/
|
*/
|
||||||
public async balance(dataTokenAddress: string, account: Account): Promise<number> {
|
public async balance(dataTokenAddress: string, account: Account): Promise<number> {
|
||||||
// TO DO
|
const datatoken = new this.web3.eth.Contract(
|
||||||
return 0
|
this.datatokensABI,
|
||||||
|
dataTokenAddress,
|
||||||
|
{ from: account.getId() }
|
||||||
|
)
|
||||||
|
const trxReceipt = await datatoken.methods.balanceOf(account.getId()).call()
|
||||||
|
return trxReceipt
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get Blob
|
||||||
|
* @param {String} dataTokenAddress
|
||||||
|
* @param {Account} account
|
||||||
|
* @return {Promise<string>} string
|
||||||
|
*/
|
||||||
|
public async getBlob(dataTokenAddress: string, account: Account): Promise<string> {
|
||||||
|
const datatoken = new this.web3.eth.Contract(
|
||||||
|
this.datatokensABI,
|
||||||
|
dataTokenAddress,
|
||||||
|
{ from: account.getId() }
|
||||||
|
)
|
||||||
|
const trxReceipt = await datatoken.methods.blob().call()
|
||||||
|
return trxReceipt
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get Name
|
||||||
|
* @param {String} dataTokenAddress
|
||||||
|
* @param {Account} account
|
||||||
|
* @return {Promise<string>} string
|
||||||
|
*/
|
||||||
|
public async getName(dataTokenAddress: string, account: Account): Promise<string> {
|
||||||
|
const datatoken = new this.web3.eth.Contract(
|
||||||
|
this.datatokensABI,
|
||||||
|
dataTokenAddress,
|
||||||
|
{ from: account.getId() }
|
||||||
|
)
|
||||||
|
const trxReceipt = await datatoken.methods.name().call()
|
||||||
|
return trxReceipt
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get Symbol
|
||||||
|
* @param {String} dataTokenAddress
|
||||||
|
* @param {Account} account
|
||||||
|
* @return {Promise<string>} string
|
||||||
|
*/
|
||||||
|
public async getSymbol(dataTokenAddress: string, account: Account): Promise<string> {
|
||||||
|
const datatoken = new this.web3.eth.Contract(
|
||||||
|
this.datatokensABI,
|
||||||
|
dataTokenAddress,
|
||||||
|
{ from: account.getId() }
|
||||||
|
)
|
||||||
|
const trxReceipt = await datatoken.methods.symbol().call()
|
||||||
|
return trxReceipt
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get Cap
|
||||||
|
* @param {String} dataTokenAddress
|
||||||
|
* @param {Account} account
|
||||||
|
* @return {Promise<string>} string
|
||||||
|
*/
|
||||||
|
public async getCap(dataTokenAddress: string, account: Account): Promise<string> {
|
||||||
|
const datatoken = new this.web3.eth.Contract(
|
||||||
|
this.datatokensABI,
|
||||||
|
dataTokenAddress,
|
||||||
|
{ from: account.getId() }
|
||||||
|
)
|
||||||
|
const trxReceipt = await datatoken.methods.cap().call()
|
||||||
|
return trxReceipt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1
src/datatokens/DatatokensABI.json
Normal file
1
src/datatokens/DatatokensABI.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
[{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"name","type":"string"},{"name":"symbol","type":"string"},{"name":"minter","type":"address"},{"name":"cap","type":"uint256"},{"name":"blob","type":"string"},{"name":"feeManager","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"constant":false,"inputs":[{"name":"name","type":"string"},{"name":"symbol","type":"string"},{"name":"minter","type":"address"},{"name":"cap","type":"uint256"},{"name":"blob","type":"string"},{"name":"feeManager","type":"address"}],"name":"initialize","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"account","type":"address"},{"name":"value","type":"uint256"}],"name":"mint","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"minter","type":"address"}],"name":"setMinter","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"blob","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"cap","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"isMinter","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isInitialized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isPaused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"}]
|
@ -1 +0,0 @@
|
|||||||
export const OceanDataTokenABI = {}
|
|
1
src/datatokens/FactoryABI.json
Normal file
1
src/datatokens/FactoryABI.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
[{"inputs":[{"name":"_template","type":"address"},{"name":"_feeManager","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newTokenAddress","type":"address"},{"indexed":false,"name":"templateAddress","type":"address"},{"indexed":false,"name":"tokenName","type":"string"}],"name":"TokenCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"tokenAddress","type":"address"},{"indexed":true,"name":"tokenName","type":"string"},{"indexed":true,"name":"tokenSymbol","type":"string"},{"indexed":false,"name":"tokenCap","type":"uint256"},{"indexed":false,"name":"RegisteredBy","type":"address"},{"indexed":false,"name":"RegisteredAt","type":"uint256"},{"indexed":false,"name":"blob","type":"string"}],"name":"TokenRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"instance","type":"address"}],"name":"InstanceDeployed","type":"event"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_symbol","type":"string"},{"name":"_cap","type":"uint256"},{"name":"_blob","type":"string"},{"name":"_minter","type":"address"}],"name":"createToken","outputs":[{"name":"token","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]
|
@ -1 +0,0 @@
|
|||||||
export const OceanFactoryABI = {}
|
|
@ -6,13 +6,13 @@ export class Config {
|
|||||||
* Aquarius URL.
|
* Aquarius URL.
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
public aquariusUri: string
|
public aquariusUri?: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Brizo URL.
|
* Brizo URL.
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
public brizoUri: string
|
public brizoUri?: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Web3 Provider.
|
* Web3 Provider.
|
||||||
@ -30,13 +30,13 @@ export class Config {
|
|||||||
* Factory ABI
|
* Factory ABI
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
public factoryABI: object
|
public factoryABI?: object
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* datatokens ABI
|
* datatokens ABI
|
||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
public datatokensABI: object
|
public datatokensABI?: object
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log level.
|
* Log level.
|
||||||
|
@ -52,7 +52,8 @@ export class Assets extends Instantiable {
|
|||||||
const publisherURI = this.ocean.brizo.getURI()
|
const publisherURI = this.ocean.brizo.getURI()
|
||||||
const jsonBlob = { t: 0, url: publisherURI }
|
const jsonBlob = { t: 0, url: publisherURI }
|
||||||
const { datatokens } = this.ocean
|
const { datatokens } = this.ocean
|
||||||
return datatokens.create(JSON.stringify(jsonBlob), publisher)
|
return datatokens.create( JSON.stringify(jsonBlob), publisher)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,7 +80,7 @@ export class Assets extends Instantiable {
|
|||||||
const metadataStoreURI = this.ocean.aquarius.getURI()
|
const metadataStoreURI = this.ocean.aquarius.getURI()
|
||||||
const jsonBlob = { t: 1, url: metadataStoreURI }
|
const jsonBlob = { t: 1, url: metadataStoreURI }
|
||||||
const { datatokens } = this.ocean
|
const { datatokens } = this.ocean
|
||||||
dtAddress = await datatokens.create(JSON.stringify(jsonBlob), publisher)
|
dtAddress = await datatokens.create({ metaDataStoreURI: JSON.stringify(jsonBlob), account: publisher })
|
||||||
this.logger.log('DataToken creted')
|
this.logger.log('DataToken creted')
|
||||||
observer.next(CreateProgressStep.DataTokenCreated)
|
observer.next(CreateProgressStep.DataTokenCreated)
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { BodyInit, RequestInit, Response } from 'node-fetch'
|
import { BodyInit, RequestInit, Response } from 'node-fetch'
|
||||||
import fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import { Logger } from '../../utils'
|
import { Logger } from '../../utils'
|
||||||
|
|
||||||
const fetch = require('node-fetch')
|
const fetch = require('node-fetch')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user