@cityofzion/bs-ethereum 1.0.4 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/BSEthereum.d.ts +10 -6
- package/dist/BSEthereum.js +60 -23
- package/dist/BitqueryBDSEthereum.d.ts +1 -2
- package/dist/BitqueryBDSEthereum.js +27 -12
- package/dist/BitqueryEDSEthereum.d.ts +1 -2
- package/dist/BitqueryEDSEthereum.js +20 -5
- package/dist/GhostMarketNDSEthereum.d.ts +1 -1
- package/dist/GhostMarketNDSEthereum.js +17 -3
- package/dist/LedgerServiceEthereum.d.ts +11 -0
- package/dist/LedgerServiceEthereum.js +81 -0
- package/dist/RpcBDSEthereum.d.ts +1 -1
- package/dist/RpcBDSEthereum.js +21 -7
- package/dist/constants.js +1 -1
- package/package.json +8 -2
- package/.eslintignore +0 -13
- package/.eslintrc.cjs +0 -22
- package/.rush/temp/operation/build/all.log +0 -1
- package/.rush/temp/operation/build/state.json +0 -3
- package/.rush/temp/package-deps_build.json +0 -28
- package/.rush/temp/shrinkwrap-deps.json +0 -449
- package/CHANGELOG.json +0 -104
- package/CHANGELOG.md +0 -57
- package/bs-ethereum.build.log +0 -1
- package/jest.config.ts +0 -13
- package/jest.setup.ts +0 -1
- package/src/BSEthereum.ts +0 -190
- package/src/BitqueryBDSEthereum.ts +0 -231
- package/src/BitqueryEDSEthereum.ts +0 -67
- package/src/GhostMarketNDSEthereum.ts +0 -125
- package/src/RpcBDSEthereum.ts +0 -88
- package/src/__tests__/BDSEthereum.spec.ts +0 -126
- package/src/__tests__/BSEthereum.spec.ts +0 -131
- package/src/__tests__/BitqueryEDSEthereum.spec.ts +0 -55
- package/src/__tests__/GhostMarketNDSEthereum.spec.ts +0 -49
- package/src/assets/tokens/common.json +0 -8
- package/src/constants.ts +0 -36
- package/src/graphql.ts +0 -288
- package/src/index.ts +0 -6
- package/tsconfig.build.json +0 -4
- package/tsconfig.json +0 -15
package/CHANGELOG.md
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
# Change Log - @cityofzion/bs-ethereum
|
|
2
|
-
|
|
3
|
-
This log was last generated on Mon, 04 Mar 2024 21:53:09 GMT and should not be manually modified.
|
|
4
|
-
|
|
5
|
-
## 1.0.4
|
|
6
|
-
Mon, 04 Mar 2024 21:53:09 GMT
|
|
7
|
-
|
|
8
|
-
### Patches
|
|
9
|
-
|
|
10
|
-
- fix wrong calculated fee
|
|
11
|
-
|
|
12
|
-
## 1.0.3
|
|
13
|
-
Wed, 28 Feb 2024 17:43:01 GMT
|
|
14
|
-
|
|
15
|
-
### Patches
|
|
16
|
-
|
|
17
|
-
- Add creator infomations in nft methods return
|
|
18
|
-
|
|
19
|
-
## 1.0.2
|
|
20
|
-
Tue, 27 Feb 2024 20:29:09 GMT
|
|
21
|
-
|
|
22
|
-
### Patches
|
|
23
|
-
|
|
24
|
-
- Remove the mining wait in the transfer method to avoid long promises that take time to resolve.
|
|
25
|
-
|
|
26
|
-
## 1.0.1
|
|
27
|
-
Thu, 22 Feb 2024 16:49:56 GMT
|
|
28
|
-
|
|
29
|
-
### Patches
|
|
30
|
-
|
|
31
|
-
- Fix getTokenPrices that is returning a wrong price for ETH token address
|
|
32
|
-
|
|
33
|
-
## 1.0.0
|
|
34
|
-
Fri, 16 Feb 2024 16:13:22 GMT
|
|
35
|
-
|
|
36
|
-
### Breaking changes
|
|
37
|
-
|
|
38
|
-
- Change bitquery providers to receive apiKey on class initialization
|
|
39
|
-
|
|
40
|
-
## 0.9.1
|
|
41
|
-
Tue, 30 Jan 2024 18:26:00 GMT
|
|
42
|
-
|
|
43
|
-
### Patches
|
|
44
|
-
|
|
45
|
-
- Fixed bug with repeated ETH token in getBalance method
|
|
46
|
-
|
|
47
|
-
## 0.9.0
|
|
48
|
-
Tue, 05 Dec 2023 18:42:10 GMT
|
|
49
|
-
|
|
50
|
-
### Minor changes
|
|
51
|
-
|
|
52
|
-
- Inserted within the TokenPricesResponse type the token hash
|
|
53
|
-
|
|
54
|
-
### Patches
|
|
55
|
-
|
|
56
|
-
- Fixed issue with the getBalance method that returned negative numbers and the wrong ETH value
|
|
57
|
-
|
package/bs-ethereum.build.log
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
Invoking: tsc --project tsconfig.build.json
|
package/jest.config.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { JestConfigWithTsJest } from 'ts-jest'
|
|
2
|
-
const config: JestConfigWithTsJest = {
|
|
3
|
-
preset: 'ts-jest',
|
|
4
|
-
testEnvironment: 'node',
|
|
5
|
-
clearMocks: true,
|
|
6
|
-
verbose: true,
|
|
7
|
-
bail: true,
|
|
8
|
-
testMatch: ['<rootDir>/**/*.spec.ts'],
|
|
9
|
-
setupFiles: ['<rootDir>/jest.setup.ts'],
|
|
10
|
-
detectOpenHandles: true,
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export default config
|
package/jest.setup.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config'
|
package/src/BSEthereum.ts
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Account,
|
|
3
|
-
AccountWithDerivationPath,
|
|
4
|
-
BSCalculableFee,
|
|
5
|
-
BSWithNameService,
|
|
6
|
-
BSWithNft,
|
|
7
|
-
BlockchainDataService,
|
|
8
|
-
BlockchainService,
|
|
9
|
-
ExchangeDataService,
|
|
10
|
-
Network,
|
|
11
|
-
NftDataService,
|
|
12
|
-
PartialBy,
|
|
13
|
-
Token,
|
|
14
|
-
TransferParam,
|
|
15
|
-
} from '@cityofzion/blockchain-service'
|
|
16
|
-
import { ethers } from 'ethers'
|
|
17
|
-
import * as ethersJsonWallets from '@ethersproject/json-wallets'
|
|
18
|
-
import * as ethersBytes from '@ethersproject/bytes'
|
|
19
|
-
import * as ethersBigNumber from '@ethersproject/bignumber'
|
|
20
|
-
import { DEFAULT_URL_BY_NETWORK_TYPE, DERIVATION_PATH, NATIVE_ASSETS, TOKENS } from './constants'
|
|
21
|
-
import { BitqueryEDSEthereum } from './BitqueryEDSEthereum'
|
|
22
|
-
import { GhostMarketNDSEthereum } from './GhostMarketNDSEthereum'
|
|
23
|
-
import { RpcBDSEthereum } from './RpcBDSEthereum'
|
|
24
|
-
import { BitqueryBDSEthereum } from './BitqueryBDSEthereum'
|
|
25
|
-
|
|
26
|
-
export class BSEthereum<BSCustomName extends string = string>
|
|
27
|
-
implements BlockchainService, BSWithNft, BSWithNameService, BSCalculableFee
|
|
28
|
-
{
|
|
29
|
-
readonly blockchainName: BSCustomName
|
|
30
|
-
readonly feeToken: Token
|
|
31
|
-
readonly derivationPath: string
|
|
32
|
-
private readonly bitqueryApiKey: string
|
|
33
|
-
|
|
34
|
-
blockchainDataService!: BlockchainDataService
|
|
35
|
-
exchangeDataService!: ExchangeDataService
|
|
36
|
-
tokens: Token[]
|
|
37
|
-
nftDataService!: NftDataService
|
|
38
|
-
network!: Network
|
|
39
|
-
|
|
40
|
-
constructor(blockchainName: BSCustomName, network: PartialBy<Network, 'url'>, bitqueryApiKey: string) {
|
|
41
|
-
this.blockchainName = blockchainName
|
|
42
|
-
this.derivationPath = DERIVATION_PATH
|
|
43
|
-
this.tokens = TOKENS[network.type]
|
|
44
|
-
this.bitqueryApiKey = bitqueryApiKey
|
|
45
|
-
|
|
46
|
-
this.feeToken = this.tokens.find(token => token.symbol === 'ETH')!
|
|
47
|
-
this.setNetwork(network)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
setNetwork(param: PartialBy<Network, 'url'>) {
|
|
51
|
-
const network = {
|
|
52
|
-
type: param.type,
|
|
53
|
-
url: param.url ?? DEFAULT_URL_BY_NETWORK_TYPE[param.type],
|
|
54
|
-
}
|
|
55
|
-
this.network = network
|
|
56
|
-
|
|
57
|
-
if (network.type === 'custom') {
|
|
58
|
-
this.blockchainDataService = new RpcBDSEthereum(network)
|
|
59
|
-
} else {
|
|
60
|
-
this.blockchainDataService = new BitqueryBDSEthereum(network, this.bitqueryApiKey)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
this.exchangeDataService = new BitqueryEDSEthereum(network.type, this.bitqueryApiKey)
|
|
64
|
-
this.nftDataService = new GhostMarketNDSEthereum(network.type)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
validateAddress(address: string): boolean {
|
|
68
|
-
return ethers.utils.isAddress(address)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
validateEncrypted(json: string): boolean {
|
|
72
|
-
return ethersJsonWallets.isCrowdsaleWallet(json) || ethersJsonWallets.isKeystoreWallet(json)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
validateKey(key: string): boolean {
|
|
76
|
-
try {
|
|
77
|
-
if (!key.startsWith('0x')) {
|
|
78
|
-
key = '0x' + key
|
|
79
|
-
}
|
|
80
|
-
if (ethersBytes.hexDataLength(key) !== 32) return false
|
|
81
|
-
|
|
82
|
-
return true
|
|
83
|
-
} catch (error) {
|
|
84
|
-
return false
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
validateNameServiceDomainFormat(domainName: string): boolean {
|
|
89
|
-
if (!domainName.endsWith('.eth')) return false
|
|
90
|
-
return true
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
generateAccountFromMnemonic(mnemonic: string[] | string, index: number): AccountWithDerivationPath {
|
|
94
|
-
const path = this.derivationPath.replace('?', index.toString())
|
|
95
|
-
const wallet = ethers.Wallet.fromMnemonic(Array.isArray(mnemonic) ? mnemonic.join(' ') : mnemonic, path)
|
|
96
|
-
|
|
97
|
-
return {
|
|
98
|
-
address: wallet.address,
|
|
99
|
-
key: wallet.privateKey,
|
|
100
|
-
type: 'privateKey',
|
|
101
|
-
derivationPath: path,
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
generateAccountFromKey(key: string): Account {
|
|
106
|
-
const wallet = new ethers.Wallet(key)
|
|
107
|
-
return {
|
|
108
|
-
address: wallet.address,
|
|
109
|
-
key,
|
|
110
|
-
type: 'privateKey',
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
async decrypt(json: string, password: string): Promise<Account> {
|
|
115
|
-
const wallet = await ethers.Wallet.fromEncryptedJson(json, password)
|
|
116
|
-
return {
|
|
117
|
-
address: wallet.address,
|
|
118
|
-
key: wallet.privateKey,
|
|
119
|
-
type: 'privateKey',
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
async encrypt(key: string, password: string): Promise<string> {
|
|
124
|
-
const wallet = new ethers.Wallet(key)
|
|
125
|
-
return wallet.encrypt(password)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
async transfer({ senderAccount, intent }: TransferParam): Promise<string> {
|
|
129
|
-
const provider = new ethers.providers.JsonRpcProvider(this.network.url)
|
|
130
|
-
const wallet = new ethers.Wallet(senderAccount.key, provider)
|
|
131
|
-
|
|
132
|
-
let transaction: ethers.providers.TransactionResponse
|
|
133
|
-
const decimals = intent.tokenDecimals ?? 18
|
|
134
|
-
const amount = ethersBigNumber.parseFixed(intent.amount, decimals)
|
|
135
|
-
|
|
136
|
-
const isNative = NATIVE_ASSETS.some(asset => asset.hash === intent.tokenHash)
|
|
137
|
-
if (!isNative) {
|
|
138
|
-
const contract = new ethers.Contract(
|
|
139
|
-
intent.tokenHash,
|
|
140
|
-
['function transfer(address to, uint amount) returns (bool)'],
|
|
141
|
-
wallet
|
|
142
|
-
)
|
|
143
|
-
transaction = await contract.transfer(intent.receiverAddress, amount)
|
|
144
|
-
} else {
|
|
145
|
-
transaction = await wallet.sendTransaction({
|
|
146
|
-
to: intent.receiverAddress,
|
|
147
|
-
value: amount,
|
|
148
|
-
})
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return transaction.hash
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
async calculateTransferFee({ senderAccount, intent }: TransferParam): Promise<string> {
|
|
155
|
-
const provider = new ethers.providers.JsonRpcProvider(this.network.url)
|
|
156
|
-
const wallet = new ethers.Wallet(senderAccount.key, provider)
|
|
157
|
-
|
|
158
|
-
const gasPrice = await provider.getGasPrice()
|
|
159
|
-
|
|
160
|
-
let estimated: ethers.BigNumber
|
|
161
|
-
|
|
162
|
-
const isNative = NATIVE_ASSETS.some(asset => asset.hash === intent.tokenHash)
|
|
163
|
-
const decimals = intent.tokenDecimals ?? 18
|
|
164
|
-
const amount = ethersBigNumber.parseFixed(intent.amount, decimals)
|
|
165
|
-
|
|
166
|
-
if (!isNative) {
|
|
167
|
-
const contract = new ethers.Contract(
|
|
168
|
-
intent.tokenHash,
|
|
169
|
-
['function transfer(address to, uint amount) returns (bool)'],
|
|
170
|
-
wallet
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
estimated = await contract.estimateGas.transfer(intent.receiverAddress, amount)
|
|
174
|
-
} else {
|
|
175
|
-
estimated = await wallet.estimateGas({
|
|
176
|
-
to: intent.receiverAddress,
|
|
177
|
-
value: amount,
|
|
178
|
-
})
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
return ethers.utils.formatEther(gasPrice.mul(estimated))
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
async resolveNameServiceDomain(domainName: string): Promise<string> {
|
|
185
|
-
const provider = new ethers.providers.JsonRpcProvider(this.network.url)
|
|
186
|
-
const address = await provider.resolveName(domainName)
|
|
187
|
-
if (!address) throw new Error('No address found for domain name')
|
|
188
|
-
return address
|
|
189
|
-
}
|
|
190
|
-
}
|
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BalanceResponse,
|
|
3
|
-
ContractResponse,
|
|
4
|
-
NetworkType,
|
|
5
|
-
Token,
|
|
6
|
-
TransactionsByAddressParams,
|
|
7
|
-
TransactionsByAddressResponse,
|
|
8
|
-
TransactionResponse,
|
|
9
|
-
TransactionTransferAsset,
|
|
10
|
-
TransactionTransferNft,
|
|
11
|
-
Network,
|
|
12
|
-
} from '@cityofzion/blockchain-service'
|
|
13
|
-
import { Client, fetchExchange } from '@urql/core'
|
|
14
|
-
import fetch from 'node-fetch'
|
|
15
|
-
import { BITQUERY_NETWORK_BY_NETWORK_TYPE, BITQUERY_URL, NATIVE_ASSETS, TOKENS } from './constants'
|
|
16
|
-
import {
|
|
17
|
-
BitqueryTransaction,
|
|
18
|
-
bitqueryGetBalanceQuery,
|
|
19
|
-
bitqueryGetTokenInfoQuery,
|
|
20
|
-
bitqueryGetTransactionQuery,
|
|
21
|
-
bitqueryGetTransactionsByAddressQuery,
|
|
22
|
-
} from './graphql'
|
|
23
|
-
import { RpcBDSEthereum } from './RpcBDSEthereum'
|
|
24
|
-
|
|
25
|
-
export class BitqueryBDSEthereum extends RpcBDSEthereum {
|
|
26
|
-
private readonly client: Client
|
|
27
|
-
private readonly networkType: Exclude<NetworkType, 'custom'>
|
|
28
|
-
|
|
29
|
-
maxTimeToConfirmTransactionInMs: number = 1000 * 60 * 8
|
|
30
|
-
|
|
31
|
-
constructor(network: Network, apiKey: string) {
|
|
32
|
-
super(network)
|
|
33
|
-
|
|
34
|
-
if (network.type === 'custom') throw new Error('Custom network not supported')
|
|
35
|
-
this.networkType = network.type
|
|
36
|
-
|
|
37
|
-
this.client = new Client({
|
|
38
|
-
url: BITQUERY_URL,
|
|
39
|
-
exchanges: [fetchExchange],
|
|
40
|
-
fetch,
|
|
41
|
-
fetchOptions: {
|
|
42
|
-
headers: {
|
|
43
|
-
'X-API-KEY': apiKey,
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
})
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async getTransaction(hash: string): Promise<TransactionResponse> {
|
|
50
|
-
const result = await this.client
|
|
51
|
-
.query(bitqueryGetTransactionQuery, {
|
|
52
|
-
hash,
|
|
53
|
-
network: BITQUERY_NETWORK_BY_NETWORK_TYPE[this.networkType],
|
|
54
|
-
})
|
|
55
|
-
.toPromise()
|
|
56
|
-
if (result.error) throw new Error(result.error.message)
|
|
57
|
-
if (!result.data || !result.data.ethereum.transfers.length) throw new Error('Transaction not found')
|
|
58
|
-
|
|
59
|
-
const transfers = result.data.ethereum.transfers.map(this.parseTransactionTransfer)
|
|
60
|
-
|
|
61
|
-
const {
|
|
62
|
-
block: {
|
|
63
|
-
height,
|
|
64
|
-
timestamp: { unixtime },
|
|
65
|
-
},
|
|
66
|
-
transaction: { gasValue, hash: transactionHash },
|
|
67
|
-
} = result.data.ethereum.transfers[0]
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
block: height,
|
|
71
|
-
time: unixtime,
|
|
72
|
-
hash: transactionHash,
|
|
73
|
-
fee: String(gasValue),
|
|
74
|
-
transfers,
|
|
75
|
-
notifications: [],
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async getTransactionsByAddress({
|
|
80
|
-
address,
|
|
81
|
-
page = 1,
|
|
82
|
-
}: TransactionsByAddressParams): Promise<TransactionsByAddressResponse> {
|
|
83
|
-
const limit = 10
|
|
84
|
-
const offset = limit * (page - 1)
|
|
85
|
-
|
|
86
|
-
const result = await this.client
|
|
87
|
-
.query(bitqueryGetTransactionsByAddressQuery, {
|
|
88
|
-
address,
|
|
89
|
-
limit,
|
|
90
|
-
offset,
|
|
91
|
-
network: BITQUERY_NETWORK_BY_NETWORK_TYPE[this.networkType],
|
|
92
|
-
})
|
|
93
|
-
.toPromise()
|
|
94
|
-
|
|
95
|
-
if (result.error) throw new Error(result.error.message)
|
|
96
|
-
if (!result.data) throw new Error('Address does not have transactions')
|
|
97
|
-
|
|
98
|
-
const totalCount =
|
|
99
|
-
(result.data.ethereum.sentCount[0].count ?? 0) + (result.data.ethereum.receiverCount[0].count ?? 0)
|
|
100
|
-
const mixedTransfers = [...(result?.data?.ethereum?.sent ?? []), ...(result?.data?.ethereum?.received ?? [])]
|
|
101
|
-
|
|
102
|
-
const transactions = new Map<string, TransactionResponse>()
|
|
103
|
-
|
|
104
|
-
mixedTransfers.forEach(transfer => {
|
|
105
|
-
const transactionTransfer = this.parseTransactionTransfer(transfer)
|
|
106
|
-
|
|
107
|
-
const existingTransaction = transactions.get(transfer.transaction.hash)
|
|
108
|
-
if (existingTransaction) {
|
|
109
|
-
existingTransaction.transfers.push(transactionTransfer)
|
|
110
|
-
return
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
transactions.set(transfer.transaction.hash, {
|
|
114
|
-
block: transfer.block.height,
|
|
115
|
-
hash: transfer.transaction.hash,
|
|
116
|
-
time: transfer.block.timestamp.unixtime,
|
|
117
|
-
fee: String(transfer.transaction.gasValue),
|
|
118
|
-
transfers: [transactionTransfer],
|
|
119
|
-
notifications: [],
|
|
120
|
-
})
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
return {
|
|
124
|
-
totalCount,
|
|
125
|
-
limit: limit * 2,
|
|
126
|
-
transactions: Array.from(transactions.values()),
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
async getContract(): Promise<ContractResponse> {
|
|
131
|
-
throw new Error("Bitquery doesn't support contract info")
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
async getTokenInfo(hash: string): Promise<Token> {
|
|
135
|
-
const localToken = TOKENS[this.networkType].find(token => token.hash === hash)
|
|
136
|
-
if (localToken) return localToken
|
|
137
|
-
|
|
138
|
-
const result = await this.client
|
|
139
|
-
.query(bitqueryGetTokenInfoQuery, {
|
|
140
|
-
hash,
|
|
141
|
-
network: BITQUERY_NETWORK_BY_NETWORK_TYPE[this.networkType],
|
|
142
|
-
})
|
|
143
|
-
.toPromise()
|
|
144
|
-
|
|
145
|
-
if (result.error) throw new Error(result.error.message)
|
|
146
|
-
if (!result.data || result.data.ethereum.smartContractCalls.length <= 0) throw new Error('Token not found')
|
|
147
|
-
|
|
148
|
-
const {
|
|
149
|
-
address: { address },
|
|
150
|
-
currency: { decimals, name, symbol, tokenType },
|
|
151
|
-
} = result.data.ethereum.smartContractCalls[0].smartContract
|
|
152
|
-
|
|
153
|
-
if (tokenType !== 'ERC20') throw new Error('Token is not ERC20')
|
|
154
|
-
|
|
155
|
-
return {
|
|
156
|
-
hash: address,
|
|
157
|
-
name,
|
|
158
|
-
symbol,
|
|
159
|
-
decimals,
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
async getBalance(address: string): Promise<BalanceResponse[]> {
|
|
164
|
-
const result = await this.client
|
|
165
|
-
.query(bitqueryGetBalanceQuery, {
|
|
166
|
-
address,
|
|
167
|
-
network: BITQUERY_NETWORK_BY_NETWORK_TYPE[this.networkType],
|
|
168
|
-
})
|
|
169
|
-
.toPromise()
|
|
170
|
-
|
|
171
|
-
if (result.error) throw new Error(result.error.message)
|
|
172
|
-
const data = result.data?.ethereum.address[0].balances ?? []
|
|
173
|
-
const ethBalance = result.data?.ethereum.address[0].balance ?? 0
|
|
174
|
-
const ethToken = NATIVE_ASSETS.find(asset => asset.symbol === 'ETH')!
|
|
175
|
-
|
|
176
|
-
const balances: BalanceResponse[] = [
|
|
177
|
-
{
|
|
178
|
-
amount: ethBalance.toString(),
|
|
179
|
-
token: ethToken,
|
|
180
|
-
},
|
|
181
|
-
]
|
|
182
|
-
|
|
183
|
-
data.forEach(({ value, currency: { address, decimals, name, symbol } }) => {
|
|
184
|
-
if (value < 0 || address === ethToken.hash) return
|
|
185
|
-
|
|
186
|
-
balances.push({
|
|
187
|
-
amount: value.toString(),
|
|
188
|
-
token: {
|
|
189
|
-
hash: address,
|
|
190
|
-
symbol,
|
|
191
|
-
name,
|
|
192
|
-
decimals,
|
|
193
|
-
},
|
|
194
|
-
})
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
return balances
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
private parseTransactionTransfer({
|
|
201
|
-
amount,
|
|
202
|
-
currency: { tokenType, address, decimals, name, symbol },
|
|
203
|
-
entityId,
|
|
204
|
-
sender,
|
|
205
|
-
receiver,
|
|
206
|
-
}: BitqueryTransaction): TransactionTransferAsset | TransactionTransferNft {
|
|
207
|
-
if (tokenType === 'ERC721') {
|
|
208
|
-
return {
|
|
209
|
-
from: sender.address,
|
|
210
|
-
to: receiver.address,
|
|
211
|
-
tokenId: entityId,
|
|
212
|
-
contractHash: address,
|
|
213
|
-
type: 'nft',
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return {
|
|
218
|
-
from: sender.address,
|
|
219
|
-
to: receiver.address,
|
|
220
|
-
contractHash: address,
|
|
221
|
-
amount: amount.toString(),
|
|
222
|
-
token: {
|
|
223
|
-
decimals: decimals,
|
|
224
|
-
hash: address,
|
|
225
|
-
name: name,
|
|
226
|
-
symbol: symbol,
|
|
227
|
-
},
|
|
228
|
-
type: 'token',
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { Currency, ExchangeDataService, NetworkType, TokenPricesResponse } from '@cityofzion/blockchain-service'
|
|
2
|
-
import { Client, fetchExchange } from '@urql/core'
|
|
3
|
-
import fetch from 'node-fetch'
|
|
4
|
-
import { BITQUERY_URL } from './constants'
|
|
5
|
-
import dayjs from 'dayjs'
|
|
6
|
-
import utc from 'dayjs/plugin/utc'
|
|
7
|
-
import { bitqueryGetPricesQuery } from './graphql'
|
|
8
|
-
|
|
9
|
-
dayjs.extend(utc)
|
|
10
|
-
export class BitqueryEDSEthereum implements ExchangeDataService {
|
|
11
|
-
private readonly client: Client
|
|
12
|
-
private readonly networkType: NetworkType
|
|
13
|
-
|
|
14
|
-
constructor(networkType: NetworkType, apiKey: string) {
|
|
15
|
-
this.networkType = networkType
|
|
16
|
-
|
|
17
|
-
this.client = new Client({
|
|
18
|
-
url: BITQUERY_URL,
|
|
19
|
-
exchanges: [fetchExchange],
|
|
20
|
-
fetch,
|
|
21
|
-
fetchOptions: {
|
|
22
|
-
headers: {
|
|
23
|
-
'X-API-KEY': apiKey,
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
})
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async getTokenPrices(currency: Currency): Promise<TokenPricesResponse[]> {
|
|
30
|
-
if (this.networkType !== 'mainnet') throw new Error('Exchange is only available on mainnet')
|
|
31
|
-
|
|
32
|
-
const twoDaysAgo = dayjs.utc().subtract(2, 'day').startOf('date').toISOString()
|
|
33
|
-
|
|
34
|
-
const result = await this.client
|
|
35
|
-
.query(bitqueryGetPricesQuery, { after: twoDaysAgo, network: 'ethereum' })
|
|
36
|
-
.toPromise()
|
|
37
|
-
if (result.error) {
|
|
38
|
-
throw new Error(result.error.message)
|
|
39
|
-
}
|
|
40
|
-
if (!result.data) {
|
|
41
|
-
throw new Error('There is no price data')
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
let currencyRatio: number = 1
|
|
45
|
-
if (currency !== 'USD') {
|
|
46
|
-
currencyRatio = await this.getCurrencyRatio(currency)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const prices = result.data.ethereum.dexTrades.map(
|
|
50
|
-
(trade): TokenPricesResponse => ({
|
|
51
|
-
symbol: trade.baseCurrency.symbol,
|
|
52
|
-
price: trade.quotePrice * currencyRatio,
|
|
53
|
-
hash: trade.baseCurrency.address,
|
|
54
|
-
})
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
return prices
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
private async getCurrencyRatio(currency: Currency): Promise<number> {
|
|
61
|
-
const request = await fetch(`https://api.flamingo.finance/fiat/exchange-rate?pair=USD_${currency}`, {
|
|
62
|
-
method: 'GET',
|
|
63
|
-
})
|
|
64
|
-
const data = await request.json()
|
|
65
|
-
return data
|
|
66
|
-
}
|
|
67
|
-
}
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
NftResponse,
|
|
3
|
-
NftsResponse,
|
|
4
|
-
NetworkType,
|
|
5
|
-
NftDataService,
|
|
6
|
-
GetNftParam,
|
|
7
|
-
GetNftsByAddressParams,
|
|
8
|
-
} from '@cityofzion/blockchain-service'
|
|
9
|
-
import qs from 'query-string'
|
|
10
|
-
|
|
11
|
-
import { GHOSTMARKET_CHAIN_BY_NETWORK_TYPE, GHOSTMARKET_URL_BY_NETWORK_TYPE } from './constants'
|
|
12
|
-
import fetch from 'node-fetch'
|
|
13
|
-
|
|
14
|
-
type GhostMarketNFT = {
|
|
15
|
-
tokenId: string
|
|
16
|
-
contract: {
|
|
17
|
-
chain?: string
|
|
18
|
-
hash: string
|
|
19
|
-
symbol: string
|
|
20
|
-
}
|
|
21
|
-
creator: {
|
|
22
|
-
address: string
|
|
23
|
-
offchainName?: string
|
|
24
|
-
}
|
|
25
|
-
apiUrl?: string
|
|
26
|
-
ownerships: {
|
|
27
|
-
owner: {
|
|
28
|
-
address?: string
|
|
29
|
-
}
|
|
30
|
-
}[]
|
|
31
|
-
collection: {
|
|
32
|
-
name?: string
|
|
33
|
-
logoUrl?: string
|
|
34
|
-
}
|
|
35
|
-
metadata: {
|
|
36
|
-
description: string
|
|
37
|
-
mediaType: string
|
|
38
|
-
mediaUri: string
|
|
39
|
-
mintDate: number
|
|
40
|
-
mintNumber: number
|
|
41
|
-
name: string
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
type GhostMarketAssets = {
|
|
46
|
-
assets: GhostMarketNFT[]
|
|
47
|
-
next: string
|
|
48
|
-
}
|
|
49
|
-
export class GhostMarketNDSEthereum implements NftDataService {
|
|
50
|
-
private networkType: NetworkType
|
|
51
|
-
|
|
52
|
-
constructor(networkType: NetworkType) {
|
|
53
|
-
this.networkType = networkType
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async getNftsByAddress({ address, size = 18, cursor }: GetNftsByAddressParams): Promise<NftsResponse> {
|
|
57
|
-
const url = this.getUrlWithParams({
|
|
58
|
-
size,
|
|
59
|
-
owners: [address],
|
|
60
|
-
cursor: cursor,
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
const request = await fetch(url, { method: 'GET' })
|
|
64
|
-
const data = (await request.json()) as GhostMarketAssets
|
|
65
|
-
const nfts = data.assets ?? []
|
|
66
|
-
|
|
67
|
-
return { nextCursor: data.next, items: nfts.map(this.parse.bind(this)) }
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async getNft({ contractHash, tokenId }: GetNftParam): Promise<NftResponse> {
|
|
71
|
-
const url = this.getUrlWithParams({
|
|
72
|
-
contract: contractHash,
|
|
73
|
-
tokenIds: [tokenId],
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
const request = await fetch(url, { method: 'GET' })
|
|
77
|
-
const data = (await request.json()) as GhostMarketAssets
|
|
78
|
-
|
|
79
|
-
return this.parse(data.assets[0])
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
private treatGhostMarketImage(srcImage?: string) {
|
|
83
|
-
if (!srcImage) {
|
|
84
|
-
return
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (srcImage.startsWith('ipfs://')) {
|
|
88
|
-
const [, imageId] = srcImage.split('://')
|
|
89
|
-
|
|
90
|
-
return `https://ghostmarket.mypinata.cloud/ipfs/${imageId}`
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return srcImage
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
private getUrlWithParams(params: any) {
|
|
97
|
-
const parameters = qs.stringify(
|
|
98
|
-
{
|
|
99
|
-
chain: GHOSTMARKET_CHAIN_BY_NETWORK_TYPE[this.networkType],
|
|
100
|
-
...params,
|
|
101
|
-
},
|
|
102
|
-
{ arrayFormat: 'bracket' }
|
|
103
|
-
)
|
|
104
|
-
return `${GHOSTMARKET_URL_BY_NETWORK_TYPE[this.networkType]}/assets?${parameters}`
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
private parse(data: GhostMarketNFT) {
|
|
108
|
-
const nftResponse: NftResponse = {
|
|
109
|
-
collectionImage: this.treatGhostMarketImage(data.collection?.logoUrl),
|
|
110
|
-
id: data.tokenId,
|
|
111
|
-
contractHash: data.contract.hash,
|
|
112
|
-
symbol: data.contract.symbol,
|
|
113
|
-
collectionName: data.collection?.name,
|
|
114
|
-
image: this.treatGhostMarketImage(data.metadata.mediaUri),
|
|
115
|
-
isSVG: String(data.metadata.mediaType).includes('svg+xml'),
|
|
116
|
-
name: data.metadata.name,
|
|
117
|
-
creator: {
|
|
118
|
-
address: data.creator.address,
|
|
119
|
-
name: data.creator.offchainName,
|
|
120
|
-
},
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return nftResponse
|
|
124
|
-
}
|
|
125
|
-
}
|