@cityofzion/bs-neo3 0.4.2 → 0.7.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.
Files changed (73) hide show
  1. package/.rush/temp/operation/build/state.json +1 -1
  2. package/.rush/temp/package-deps_build.json +20 -0
  3. package/.rush/temp/shrinkwrap-deps.json +2 -2
  4. package/bs-neo3.build.log +1 -86
  5. package/dist/BSNeo3.d.ts +22 -12
  6. package/dist/BSNeo3.js +85 -55
  7. package/dist/constants.d.ts +10 -3
  8. package/docs/assets/search.js +1 -1
  9. package/docs/classes/BDSNeo3.html +13 -13
  10. package/docs/classes/BSNeo3.html +80 -36
  11. package/docs/interfaces/DoraNeo3Abi.html +6 -6
  12. package/docs/interfaces/DoraNeo3Asset.html +9 -9
  13. package/docs/interfaces/DoraNeo3AssetState.html +6 -6
  14. package/docs/interfaces/DoraNeo3Balance.html +5 -5
  15. package/docs/interfaces/DoraNeo3ConsensusNode.html +3 -3
  16. package/docs/interfaces/DoraNeo3Contract.html +8 -8
  17. package/docs/interfaces/DoraNeo3Event.html +3 -3
  18. package/docs/interfaces/DoraNeo3Features.html +1 -1
  19. package/docs/interfaces/DoraNeo3HistoryState.html +3 -3
  20. package/docs/interfaces/DoraNeo3Invocation.html +3 -3
  21. package/docs/interfaces/DoraNeo3Item.html +11 -11
  22. package/docs/interfaces/DoraNeo3Manifest.html +9 -9
  23. package/docs/interfaces/DoraNeo3Metadata.html +12 -12
  24. package/docs/interfaces/DoraNeo3Method.html +12 -12
  25. package/docs/interfaces/DoraNeo3Nef.html +7 -7
  26. package/docs/interfaces/DoraNeo3Notification.html +4 -4
  27. package/docs/interfaces/DoraNeo3Parameter.html +3 -3
  28. package/docs/interfaces/DoraNeo3Permission.html +3 -3
  29. package/docs/interfaces/DoraNeo3Signer.html +3 -3
  30. package/docs/interfaces/DoraNeo3Token.html +6 -6
  31. package/docs/interfaces/DoraNeo3Transaction.html +16 -16
  32. package/docs/interfaces/DoraNeo3TransactionHistory.html +3 -3
  33. package/docs/interfaces/DoraNeo3Transfer.html +9 -9
  34. package/docs/interfaces/DoraNeo3Witness.html +3 -3
  35. package/docs/variables/DORA_ASSET.html +1 -1
  36. package/docs/variables/DORA_BALANCE.html +1 -1
  37. package/docs/variables/DORA_CONTRACT.html +1 -1
  38. package/docs/variables/DORA_NODES.html +1 -1
  39. package/docs/variables/DORA_TRANSACTION.html +1 -1
  40. package/docs/variables/DORA_TRANSACTIONS.html +1 -1
  41. package/docs/variables/explorerOptions.html +1 -1
  42. package/docs/variables/gasInfoNeo3.html +11 -2
  43. package/docs/variables/neoInfoNeo3.html +11 -2
  44. package/jest.config.ts +13 -0
  45. package/jest.setup.ts +1 -0
  46. package/package.json +14 -6
  47. package/src/BSNeo3.ts +221 -221
  48. package/src/DoraBDSNeo3.ts +180 -0
  49. package/src/FlamingoEDSNeo3.ts +45 -0
  50. package/src/GhostMarketNDSNeo3.ts +117 -0
  51. package/src/RpcBDSNeo3.ts +155 -0
  52. package/src/__tests__/BDSNeo3.spec.ts +126 -0
  53. package/src/__tests__/BSNeo3.spec.ts +142 -0
  54. package/src/__tests__/FlamingoEDSNeo3.spec.ts +45 -0
  55. package/src/__tests__/GhostMarketNDSNeo3.spec.ts +43 -0
  56. package/src/__tests__/utils/sleep.ts +1 -0
  57. package/src/assets/tokens/common.json +14 -0
  58. package/src/assets/tokens/mainnet.json +116 -0
  59. package/src/constants.ts +26 -10
  60. package/src/index.ts +4 -2
  61. package/tsconfig.build.json +4 -0
  62. package/tsconfig.json +3 -7
  63. package/bs-neo3.build.error.log +0 -84
  64. package/dist/excpetions.d.ts +0 -3
  65. package/dist/excpetions.js +0 -8
  66. package/src/BDSNeo3.ts +0 -168
  67. package/src/assets/blockchain_icon_neo.png +0 -0
  68. package/src/assets/blockchain_icon_neo_white.png +0 -0
  69. package/src/assets/tokens.json +0 -129
  70. package/src/exceptions.ts +0 -5
  71. package/src/explorer/dora/DoraNeo3Responses.ts +0 -207
  72. package/src/explorer/dora/DoraNeo3Routes.ts +0 -6
  73. package/src/explorer/index.ts +0 -8
package/src/BSNeo3.ts CHANGED
@@ -1,223 +1,223 @@
1
- import { BlockchainDataService, keychain, BlockchainService, CalculateTransferFeeDetails, SendTransactionParam, TokenInfo, Claimable, Account, Exchange, BDSClaimable, exchangeOptions, Token, IntentTransactionParam, NeoNameService, NNSRecordTypes, CalculateTransferFeeResponse } from '@cityofzion/blockchain-service'
2
- import { api, rpc, tx, u, wallet } from '@cityofzion/neon-js'
3
- import { gasInfoNeo3, neoInfoNeo3 } from './constants'
4
- import { claimGasExceptions } from './exceptions'
5
- import { explorerOptions } from './explorer'
6
- import tokens from './assets/tokens.json'
1
+ import {
2
+ BlockchainDataService,
3
+ BlockchainService,
4
+ BSClaimable,
5
+ Account,
6
+ ExchangeDataService,
7
+ BDSClaimable,
8
+ Token,
9
+ BSWithNameService,
10
+ Network,
11
+ PartialBy,
12
+ TransferParam,
13
+ BSCalculableFee,
14
+ NftDataService,
15
+ BSWithNft,
16
+ AccountWithDerivationPath,
17
+ } from '@cityofzion/blockchain-service'
18
+ import { api, u, wallet } from '@cityofzion/neon-js'
19
+ import Neon from '@cityofzion/neon-core'
7
20
  import { NeonInvoker } from '@cityofzion/neon-invoker'
8
- import {NeonParser} from "@cityofzion/neon-parser"
21
+ import { NeonParser } from '@cityofzion/neon-parser'
9
22
  import { ABI_TYPES } from '@cityofzion/neo3-parser'
10
-
11
- const NEO_NS_HASH = "0x50ac1c37690cc2cfc594472833cf57505d5f46de";
12
-
13
- export class BSNeo3<BSCustomName extends string = string> implements BlockchainService, Claimable, NeoNameService {
14
- blockchainName: BSCustomName
15
- dataService: BlockchainDataService & BDSClaimable = explorerOptions.dora
16
- feeToken: TokenInfo = gasInfoNeo3
17
- exchange: Exchange = exchangeOptions.flamingo
18
- tokenClaim: TokenInfo = neoInfoNeo3
19
- tokens: Token[] = tokens
20
-
21
- private derivationPath: string = "m/44'/888'/0'/0/?"
22
-
23
- constructor(blockchainName: BSCustomName) {
24
- this.blockchainName = blockchainName
25
- }
26
- validateAddress(address: string): boolean {
27
- return wallet.isAddress(address, 53)
28
- }
29
- validateEncryptedKey(encryptedKey: string): boolean {
30
- return wallet.isNEP2(encryptedKey)
31
- }
32
- validateWif(wif: string): boolean {
33
- return wallet.isWIF(wif)
34
- }
35
- validateNNSFormat(domainName: string): boolean {
36
- if (!domainName.endsWith('.neo')) return false
37
- return true
38
- }
39
- generateMnemonic(): string[] {
40
- keychain.generateMnemonic(128)
41
- if (!keychain.mnemonic) throw new Error("Failed to generate mnemonic")
42
- return keychain.mnemonic.toString().split(' ')
43
- }
44
- generateAccount(mnemonic: string[], index: number): Account {
45
- keychain.importMnemonic(mnemonic.join(' '))
46
- const childKey = keychain.generateChildKey("neo", this.derivationPath.replace('?', index.toString()))
47
- const wif = childKey.getWIF()
48
- const { address } = new wallet.Account(wif)
49
- return { address, wif }
50
- }
51
- generateAccountFromWif(wif: string): Account {
52
- const { address } = new wallet.Account(wif)
53
- return { address, wif }
54
- }
55
- async decryptKey(encryptedKey: string, password: string): Promise<Account> {
56
- const wif = await wallet.decrypt(encryptedKey, password)
57
- const { address } = new wallet.Account(wif)
58
- return { address, wif }
59
- }
60
- async calculateTransferFee(param: SendTransactionParam): Promise<CalculateTransferFeeResponse> {
61
- const node = await this.dataService.getHigherNode()
62
- const url = node.url
63
- const rpcClient = new rpc.NeoServerRpcClient(url)
64
- const intents = this.buildTransfer(param.transactionIntents, param.senderAccount)
65
- const txBuilder = new api.TransactionBuilder()
66
- for (const intent of intents) {
67
- if (intent.decimalAmt) {
68
- const [tokenInfo] = await api.getTokenInfos(
69
- [intent.contractHash],
70
- rpcClient
71
- )
72
- const amt = u.BigInteger.fromDecimal(
73
- intent.decimalAmt,
74
- tokenInfo.decimals
75
- )
76
- txBuilder.addNep17Transfer(
77
- intent.from,
78
- intent.to,
79
- intent.contractHash,
80
- amt
81
- )
82
- }
83
- }
84
- const txn = txBuilder.build()
85
- const accountScriptHash = wallet.getScriptHashFromAddress(param.senderAccount.address)
86
- const invokeFunctionResponse = await rpcClient.invokeScript(
87
- u.HexString.fromHex(txn.script),
88
- [
89
- {
90
- account: accountScriptHash,
91
- scopes: String(tx.WitnessScope.CalledByEntry)
92
- }
93
- ]
94
- )
95
- const systemFee = u.BigInteger.fromNumber(
96
- invokeFunctionResponse.gasconsumed
97
- ).toDecimal(gasInfoNeo3.decimals)
98
-
99
- const networkFeeResponse = await rpcClient.calculateNetworkFee(txn)
100
-
101
- const networkFee = (Number(networkFeeResponse) / 10 ** gasInfoNeo3.decimals)
102
-
103
- const sumFee = Number(systemFee) + networkFee
104
-
105
- const result: { result: number, details?: CalculateTransferFeeDetails } = {
106
- result: sumFee,
107
- details: {
108
- networkFee: networkFee.toString(),
109
- systemFee
110
- }
111
- }
112
- return result
113
- }
114
- async sendTransaction(param: SendTransactionParam): Promise<string> {
115
- try {
116
- const { senderAccount, transactionIntents } = param
117
- const node = await this.dataService.getHigherNode()
118
- const facade = await api.NetworkFacade.fromConfig({ node: node.url })
119
- const intents = this.buildTransfer(transactionIntents, senderAccount)
120
- const signing = this.signTransfer(senderAccount)
121
- const result = await facade.transferToken(intents, signing)
122
- return result;
123
- } catch (error) {
124
- throw error;
125
- }
126
- }
127
- //Claimable interface implementation
128
- async claim(account: Account): Promise<{ txid: string; symbol: string; hash: string; }> {
129
- const balance = await this.dataService.getBalance(account.address)
130
- const neoHash = neoInfoNeo3.hash
131
- const neoBalance = balance.find(balance => balance.hash === neoHash)
132
- const gasBalance = balance.find(balance => balance.hash === gasInfoNeo3.hash)
133
- const neoAccount = new wallet.Account(account.wif)
134
-
135
- if (!neoBalance || !gasBalance) throw new Error(`Problem to claim`);
136
-
137
- const dataToClaim: SendTransactionParam = {
138
- transactionIntents: [{ amount: neoBalance.amount, receiverAddress: account.address, tokenHash: neoBalance.hash }],
139
- senderAccount: account
140
- }
141
-
142
- const feeToClaim = await this.calculateTransferFee(dataToClaim)
143
-
144
- if (gasBalance.amount < feeToClaim.result) {
145
- claimGasExceptions.InsuficientGas(String(gasBalance.amount), String(feeToClaim.result))
146
- }
147
- const url = (await this.dataService.getHigherNode()).url
148
- const facade = await api.NetworkFacade.fromConfig({ node: url })
149
- const signing = this.signTransfer(account)
150
- const txid = await facade.claimGas(neoAccount, signing)
151
- const result: { txid: string; symbol: string; hash: string; } = {
152
- hash: gasInfoNeo3.hash,
153
- symbol: gasInfoNeo3.symbol,
154
- txid
155
- }
156
- return result;
157
- }
158
- // Gets the record of a second-level domain or its subdomains with the specific type.
159
- async getNNSRecord(
160
- domainName: string,
161
- type: NNSRecordTypes
162
- ): Promise<any> {
163
- const higherNode = await this.dataService.getHigherNode()
164
- const parser = NeonParser
165
- const invoker = await NeonInvoker.init(higherNode.url)
166
- const response = await invoker.testInvoke({
167
- invocations: [{
168
- scriptHash: NEO_NS_HASH,
169
- operation: "getRecord",
170
- args: [{ type: "String", value: domainName }, { type: "Integer", value: type }]
171
- }]
172
- })
173
-
174
- if (response.stack.length === 0) {
175
- throw new Error(response.exception ?? 'unrecognized response')
176
- }
177
-
178
- const parsed = parser.parseRpcResponse(response.stack[0] as any)
179
- return parsed
180
- }
181
-
182
- async getOwnerOfNNS(domainName: string): Promise<any> {
183
- const higherNode = await this.dataService.getHigherNode()
184
- const parser = NeonParser
185
- const invoker = await NeonInvoker.init(higherNode.url)
186
- const response = await invoker.testInvoke({
187
- invocations: [{
188
- scriptHash: NEO_NS_HASH,
189
- operation: "ownerOf",
190
- args: [{ type: "String", value: domainName }]
191
- }]
192
- })
193
-
194
- if (response.stack.length === 0) {
195
- throw new Error(response.exception ?? 'unrecognized response')
196
- }
197
-
198
- const parsed = parser.parseRpcResponse(response.stack[0] as any, {type: ABI_TYPES.HASH160.name})
199
- const address = parser.accountInputToAddress(parsed.replace("0x", ""))
200
- return address
201
- }
202
- private buildTransfer(transactionIntents: IntentTransactionParam[], account: Account) {
203
- const intents: api.Nep17TransferIntent[] = []
204
- const neoAccount = new wallet.Account(account.wif)
205
- for (const transactionIntent of transactionIntents) {
206
- const { amount, receiverAddress, tokenHash } = transactionIntent
207
- intents.push({
208
- to: receiverAddress,
209
- contractHash: tokenHash,
210
- from: neoAccount,
211
- decimalAmt: amount,
212
- })
213
- }
214
- return intents
215
- }
216
- private signTransfer(account: Account) {
217
- const neoAccount = new wallet.Account(account.address)
218
- const result: api.signingConfig = {
219
- signingCallback: api.signWithAccount(neoAccount)
220
- }
221
- return result
222
- }
223
- }
23
+ import { ContractInvocation } from '@cityofzion/neo3-invoker'
24
+
25
+ import { RPCBDSNeo3 } from './RpcBDSNeo3'
26
+ import { DoraBDSNeo3 } from './DoraBDSNeo3'
27
+ import { DEFAULT_URL_BY_NETWORK_TYPE, DERIVATION_PATH, NEO_NS_HASH, TOKENS } from './constants'
28
+ import { FlamingoEDSNeo3 } from './FlamingoEDSNeo3'
29
+ import { GhostMarketNDSNeo3 } from './GhostMarketNDSNeo3'
30
+ import { keychain } from '@cityofzion/bs-asteroid-sdk'
31
+
32
+ export class BSNeo3<BSCustomName extends string = string>
33
+ implements BlockchainService, BSClaimable, BSWithNameService, BSCalculableFee, BSWithNft
34
+ {
35
+ readonly blockchainName: BSCustomName
36
+ readonly feeToken: Token
37
+ readonly claimToken: Token
38
+ readonly burnToken: Token
39
+ readonly derivationPath: string
40
+
41
+ blockchainDataService!: BlockchainDataService & BDSClaimable
42
+ nftDataService!: NftDataService
43
+ exchangeDataService!: ExchangeDataService
44
+ tokens: Token[]
45
+ network!: Network
46
+
47
+ constructor(blockchainName: BSCustomName, network: PartialBy<Network, 'url'>) {
48
+ this.blockchainName = blockchainName
49
+ this.tokens = TOKENS[network.type]
50
+
51
+ this.derivationPath = DERIVATION_PATH
52
+ this.feeToken = this.tokens.find(token => token.symbol === 'GAS')!
53
+ this.burnToken = this.tokens.find(token => token.symbol === 'NEO')!
54
+ this.claimToken = this.tokens.find(token => token.symbol === 'GAS')!
55
+ this.setNetwork(network)
56
+ }
57
+
58
+ setNetwork(param: PartialBy<Network, 'url'>) {
59
+ const network = {
60
+ type: param.type,
61
+ url: param.url ?? DEFAULT_URL_BY_NETWORK_TYPE[param.type],
62
+ }
63
+ this.network = network
64
+
65
+ if (network.type === 'custom') {
66
+ this.blockchainDataService = new RPCBDSNeo3(network, this.feeToken, this.claimToken)
67
+ } else {
68
+ this.blockchainDataService = new DoraBDSNeo3(network, this.feeToken, this.claimToken)
69
+ }
70
+
71
+ this.exchangeDataService = new FlamingoEDSNeo3(network.type)
72
+ this.nftDataService = new GhostMarketNDSNeo3(network.type)
73
+ }
74
+
75
+ validateAddress(address: string): boolean {
76
+ return wallet.isAddress(address, 53)
77
+ }
78
+
79
+ validateEncrypted(encryptedKey: string): boolean {
80
+ return wallet.isNEP2(encryptedKey)
81
+ }
82
+
83
+ validateKey(key: string): boolean {
84
+ return wallet.isWIF(key) || wallet.isPrivateKey(key)
85
+ }
86
+
87
+ validateNameServiceDomainFormat(domainName: string): boolean {
88
+ if (!domainName.endsWith('.neo')) return false
89
+ return true
90
+ }
91
+
92
+ generateAccountFromMnemonic(mnemonic: string[] | string, index: number): AccountWithDerivationPath {
93
+ keychain.importMnemonic(Array.isArray(mnemonic) ? mnemonic.join(' ') : mnemonic)
94
+ const path = this.derivationPath.replace('?', index.toString())
95
+ const childKey = keychain.generateChildKey('neo', path)
96
+ const key = childKey.getWIF()
97
+ const { address } = new wallet.Account(key)
98
+ return { address, key, type: 'wif', derivationPath: path }
99
+ }
100
+
101
+ generateAccountFromKey(key: string): Account {
102
+ const type = wallet.isWIF(key) ? 'wif' : wallet.isPrivateKey(key) ? 'privateKey' : undefined
103
+ if (!type) throw new Error('Invalid key')
104
+
105
+ const { address } = new wallet.Account(key)
106
+ return { address, key, type }
107
+ }
108
+
109
+ async decrypt(encryptedKey: string, password: string): Promise<Account> {
110
+ let BsReactNativeDecrypt: any
111
+
112
+ try {
113
+ const { NativeModules } = require('react-native')
114
+ BsReactNativeDecrypt = NativeModules.BsReactNativeDecrypt
115
+ } catch {
116
+ const key = await wallet.decrypt(encryptedKey, password)
117
+ return this.generateAccountFromKey(key)
118
+ }
119
+
120
+ if (!BsReactNativeDecrypt) {
121
+ throw new Error('@CityOfZion/bs-react-native-decrypt is not installed')
122
+ }
123
+
124
+ const privateKey = await BsReactNativeDecrypt.decryptNeo3(encryptedKey, password)
125
+ return this.generateAccountFromKey(privateKey)
126
+ }
127
+
128
+ async calculateTransferFee(param: TransferParam): Promise<string> {
129
+ const account = new wallet.Account(param.senderAccount.key)
130
+ const invoker = await NeonInvoker.init({
131
+ rpcAddress: this.network.url,
132
+ account,
133
+ })
134
+
135
+ const invocations = this.buildTransferInvocation(param, account)
136
+
137
+ const { networkFee, systemFee } = await invoker.calculateFee({
138
+ invocations,
139
+ signers: [],
140
+ })
141
+
142
+ return networkFee.add(systemFee).toDecimal(this.feeToken.decimals)
143
+ }
144
+
145
+ async transfer(param: TransferParam): Promise<string> {
146
+ const account = new wallet.Account(param.senderAccount.key)
147
+ const invoker = await NeonInvoker.init({
148
+ rpcAddress: this.network.url,
149
+ account,
150
+ })
151
+
152
+ const invocations = this.buildTransferInvocation(param, account)
153
+
154
+ const transactionHash = await invoker.invokeFunction({
155
+ invocations,
156
+ signers: [],
157
+ })
158
+
159
+ return transactionHash
160
+ }
161
+
162
+ async claim(account: Account): Promise<string> {
163
+ const neoAccount = new wallet.Account(account.key)
164
+ const facade = await api.NetworkFacade.fromConfig({ node: this.network.url })
165
+
166
+ const transactionHash = await facade.claimGas(neoAccount, {
167
+ signingCallback: api.signWithAccount(neoAccount),
168
+ })
169
+
170
+ return transactionHash
171
+ }
172
+
173
+ async resolveNameServiceDomain(domainName: string): Promise<any> {
174
+ const parser = NeonParser
175
+ const invoker = await NeonInvoker.init({ rpcAddress: this.network.url })
176
+ const response = await invoker.testInvoke({
177
+ invocations: [
178
+ {
179
+ scriptHash: NEO_NS_HASH,
180
+ operation: 'ownerOf',
181
+ args: [{ type: 'String', value: domainName }],
182
+ },
183
+ ],
184
+ })
185
+
186
+ if (response.stack.length === 0) {
187
+ throw new Error(response.exception ?? 'unrecognized response')
188
+ }
189
+
190
+ const parsed = parser.parseRpcResponse(response.stack[0] as any, {
191
+ type: ABI_TYPES.HASH160.name,
192
+ })
193
+ const address = parser.accountInputToAddress(parsed.replace('0x', ''))
194
+ return address
195
+ }
196
+
197
+ private buildTransferInvocation(
198
+ { intent, tipIntent }: TransferParam,
199
+ account: Neon.wallet.Account
200
+ ): ContractInvocation[] {
201
+ const intents = [intent, ...(tipIntent ? [tipIntent] : [])]
202
+
203
+ const invocations: ContractInvocation[] = intents.map(intent => {
204
+ return {
205
+ operation: 'transfer',
206
+ scriptHash: intent.tokenHash,
207
+ args: [
208
+ { type: 'Hash160', value: account.address },
209
+ { type: 'Hash160', value: intent.receiverAddress },
210
+ {
211
+ type: 'Integer',
212
+ value: intent.tokenDecimals
213
+ ? u.BigInteger.fromDecimal(intent.amount, intent.tokenDecimals).toString()
214
+ : intent.amount,
215
+ },
216
+ { type: 'Any', value: '' },
217
+ ],
218
+ }
219
+ })
220
+
221
+ return invocations
222
+ }
223
+ }
@@ -0,0 +1,180 @@
1
+ import {
2
+ BalanceResponse,
3
+ ContractResponse,
4
+ TransactionsByAddressParams,
5
+ TransactionsByAddressResponse,
6
+ TransactionResponse,
7
+ TransactionNotifications,
8
+ Network,
9
+ Token,
10
+ TransactionTransferNft,
11
+ TransactionTransferAsset,
12
+ } from '@cityofzion/blockchain-service'
13
+ import { wallet, u } from '@cityofzion/neon-js'
14
+ import { NeoRESTApi } from '@cityofzion/dora-ts/dist/api'
15
+ import { RPCBDSNeo3 } from './RpcBDSNeo3'
16
+ import { TOKENS } from './constants'
17
+
18
+ const NeoRest = new NeoRESTApi({
19
+ doraUrl: 'https://dora.coz.io',
20
+ endpoint: '/api/v2/neo3',
21
+ })
22
+
23
+ export class DoraBDSNeo3 extends RPCBDSNeo3 {
24
+ readonly network: Network
25
+
26
+ constructor(network: Network, feeToken: Token, claimToken: Token) {
27
+ if (network.type === 'custom') {
28
+ throw new Error('DoraBDSNeo3 does not support custom networks')
29
+ }
30
+
31
+ super(network, feeToken, claimToken)
32
+ this.network = network
33
+ }
34
+
35
+ async getTransaction(hash: string): Promise<TransactionResponse> {
36
+ try {
37
+ const data = await NeoRest.transaction(hash, this.network.type)
38
+ return {
39
+ block: data.block,
40
+ time: Number(data.time),
41
+ hash: data.hash,
42
+ fee: u.BigInteger.fromNumber(data.netfee ?? 0)
43
+ .add(u.BigInteger.fromNumber(data.sysfee ?? 0))
44
+ .toDecimal(this.feeToken.decimals),
45
+ notifications: [],
46
+ transfers: [],
47
+ }
48
+ } catch {
49
+ throw new Error(`Transaction not found: ${hash}`)
50
+ }
51
+ }
52
+
53
+ async getTransactionsByAddress({
54
+ address,
55
+ page = 1,
56
+ }: TransactionsByAddressParams): Promise<TransactionsByAddressResponse> {
57
+ const data = await NeoRest.addressTXFull(address, page, this.network.type)
58
+ const transactions = await Promise.all(
59
+ data.items.map(async (item): Promise<TransactionResponse> => {
60
+ const filteredTransfers = item.notifications.filter(
61
+ item => item.event_name === 'Transfer' && (item.state.value.length === 3 || item.state.value.length === 4)
62
+ )
63
+ const transferPromises = filteredTransfers.map<Promise<TransactionTransferAsset | TransactionTransferNft>>(
64
+ async ({ contract: contractHash, state: { value: properties } }) => {
65
+ const isAsset = properties.length === 3
66
+
67
+ const from = properties[0].value
68
+ const to = properties[1].value
69
+ const convertedFrom = from ? this.convertByteStringToAddress(from) : 'Mint'
70
+ const convertedTo = to ? this.convertByteStringToAddress(to) : 'Burn'
71
+
72
+ if (isAsset) {
73
+ const token = await this.getTokenInfo(contractHash)
74
+ const [, , { value: amount }] = properties
75
+ return {
76
+ amount: u.BigInteger.fromNumber(amount).toDecimal(token.decimals ?? 0),
77
+ from: convertedFrom,
78
+ to: convertedTo,
79
+ contractHash,
80
+ type: 'token',
81
+ token,
82
+ }
83
+ }
84
+
85
+ return {
86
+ from: convertedFrom,
87
+ to: convertedTo,
88
+ tokenId: properties[3].value,
89
+ contractHash,
90
+ type: 'nft',
91
+ }
92
+ }
93
+ )
94
+ const transfers = await Promise.all(transferPromises)
95
+
96
+ const notifications = item.notifications.map<TransactionNotifications>(notification => ({
97
+ eventName: notification.event_name,
98
+ state: notification.state as any,
99
+ }))
100
+
101
+ return {
102
+ block: item.block,
103
+ time: Number(item.time),
104
+ hash: item.hash,
105
+ fee: u.BigInteger.fromNumber(item.netfee ?? 0)
106
+ .add(u.BigInteger.fromNumber(item.sysfee ?? 0))
107
+ .toDecimal(this.feeToken.decimals),
108
+ transfers,
109
+ notifications,
110
+ }
111
+ })
112
+ )
113
+
114
+ return {
115
+ totalCount: data.totalCount,
116
+ transactions,
117
+ limit: 15,
118
+ }
119
+ }
120
+
121
+ async getContract(contractHash: string): Promise<ContractResponse> {
122
+ try {
123
+ const data = await NeoRest.contract(contractHash, this.network.type)
124
+ return {
125
+ hash: data.hash,
126
+ methods: data.manifest.abi?.methods ?? [],
127
+ name: data.manifest.name,
128
+ }
129
+ } catch {
130
+ throw new Error(`Contract not found: ${contractHash}`)
131
+ }
132
+ }
133
+
134
+ async getTokenInfo(tokenHash: string): Promise<Token> {
135
+ const localToken = TOKENS[this.network.type].find(token => token.hash === tokenHash)
136
+ if (localToken) return localToken
137
+
138
+ if (this.tokenCache.has(tokenHash)) {
139
+ return this.tokenCache.get(tokenHash)!
140
+ }
141
+
142
+ try {
143
+ const { decimals, symbol, name, scripthash } = await NeoRest.asset(tokenHash, this.network.type)
144
+ const token = {
145
+ decimals: Number(decimals),
146
+ symbol,
147
+ name,
148
+ hash: scripthash,
149
+ }
150
+ this.tokenCache.set(tokenHash, token)
151
+
152
+ return token
153
+ } catch {
154
+ throw new Error(`Token not found: ${tokenHash}`)
155
+ }
156
+ }
157
+
158
+ async getBalance(address: string): Promise<BalanceResponse[]> {
159
+ const response = await NeoRest.balance(address, this.network.type)
160
+
161
+ const promises = response.map<Promise<BalanceResponse | undefined>>(async balance => {
162
+ try {
163
+ const token = await this.getTokenInfo(balance.asset)
164
+ return {
165
+ amount: u.BigInteger.fromNumber(balance.balance).toDecimal(token.decimals),
166
+ token,
167
+ }
168
+ } catch {}
169
+ })
170
+ const balances = await Promise.all(promises)
171
+ const filteredBalances = balances.filter(balance => balance !== undefined) as BalanceResponse[]
172
+ return filteredBalances
173
+ }
174
+
175
+ private convertByteStringToAddress(byteString: string): string {
176
+ const account = new wallet.Account(u.reverseHex(u.HexString.fromBase64(byteString).toString()))
177
+
178
+ return account.address
179
+ }
180
+ }