@cityofzion/bs-neo3 0.10.0 → 0.11.1
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/BSNeo3.d.ts +3 -2
- package/dist/BSNeo3.js +23 -6
- package/dist/DoraBDSNeo3.d.ts +0 -1
- package/dist/DoraBDSNeo3.js +11 -12
- package/dist/DoraESNeo3.d.ts +1 -1
- package/dist/DoraESNeo3.js +19 -5
- package/dist/FlamingoEDSNeo3.d.ts +1 -2
- package/dist/FlamingoEDSNeo3.js +20 -5
- package/dist/GhostMarketNDSNeo3.d.ts +1 -1
- package/dist/GhostMarketNDSNeo3.js +17 -3
- package/dist/LedgerServiceNeo3.d.ts +3 -1
- package/dist/LedgerServiceNeo3.js +3 -0
- package/dist/RpcBDSNeo3.d.ts +4 -4
- package/dist/RpcBDSNeo3.js +17 -17
- package/package.json +6 -3
- 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 -32
- package/.rush/temp/shrinkwrap-deps.json +0 -520
- package/CHANGELOG.json +0 -83
- package/CHANGELOG.md +0 -40
- package/bs-neo3.build.log +0 -1
- package/jest.config.ts +0 -13
- package/jest.setup.ts +0 -1
- package/src/BSNeo3.ts +0 -254
- package/src/DoraBDSNeo3.ts +0 -188
- package/src/DoraESNeo3.ts +0 -19
- package/src/FlamingoEDSNeo3.ts +0 -41
- package/src/GhostMarketNDSNeo3.ts +0 -121
- package/src/LedgerServiceNeo3.ts +0 -107
- package/src/RpcBDSNeo3.ts +0 -161
- package/src/__tests__/BDSNeo3.spec.ts +0 -124
- package/src/__tests__/BSNeo3.spec.ts +0 -175
- package/src/__tests__/DoraESNeo3.spec.ts +0 -23
- package/src/__tests__/FlamingoEDSNeo3.spec.ts +0 -48
- package/src/__tests__/GhostMarketNDSNeo3.spec.ts +0 -48
- package/src/__tests__/utils/sleep.ts +0 -1
- package/src/assets/tokens/common.json +0 -14
- package/src/assets/tokens/mainnet.json +0 -116
- package/src/constants.ts +0 -29
- package/src/index.ts +0 -6
- package/tsconfig.build.json +0 -4
- package/tsconfig.json +0 -14
|
@@ -1,121 +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
|
-
import axios from 'axios'
|
|
11
|
-
|
|
12
|
-
import { GHOSTMARKET_CHAIN_BY_NETWORK_TYPE, GHOSTMARKET_URL_BY_NETWORK_TYPE } from './constants'
|
|
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
|
-
|
|
50
|
-
export class GhostMarketNDSNeo3 implements NftDataService {
|
|
51
|
-
private networkType: NetworkType
|
|
52
|
-
|
|
53
|
-
constructor(networkType: NetworkType) {
|
|
54
|
-
this.networkType = networkType
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async getNftsByAddress({ address, size = 18, cursor }: GetNftsByAddressParams): Promise<NftsResponse> {
|
|
58
|
-
const url = this.getUrlWithParams({
|
|
59
|
-
size,
|
|
60
|
-
owners: [address],
|
|
61
|
-
cursor: cursor,
|
|
62
|
-
})
|
|
63
|
-
const { data } = await axios.get<GhostMarketAssets>(url)
|
|
64
|
-
const nfts = data.assets ?? []
|
|
65
|
-
|
|
66
|
-
return { nextCursor: data.next, items: nfts.map(this.parse.bind(this)) }
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async getNft({ contractHash, tokenId }: GetNftParam): Promise<NftResponse> {
|
|
70
|
-
const url = this.getUrlWithParams({
|
|
71
|
-
contract: contractHash,
|
|
72
|
-
tokenIds: [tokenId],
|
|
73
|
-
})
|
|
74
|
-
const { data } = await axios.get<GhostMarketAssets>(url)
|
|
75
|
-
return this.parse(data.assets[0])
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
private treatGhostMarketImage(srcImage?: string) {
|
|
79
|
-
if (!srcImage) {
|
|
80
|
-
return
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (srcImage.startsWith('ipfs://')) {
|
|
84
|
-
const [, imageId] = srcImage.split('://')
|
|
85
|
-
|
|
86
|
-
return `https://ghostmarket.mypinata.cloud/ipfs/${imageId}`
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return srcImage
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
private getUrlWithParams(params: Record<string, any>) {
|
|
93
|
-
const parameters = qs.stringify(
|
|
94
|
-
{
|
|
95
|
-
chain: GHOSTMARKET_CHAIN_BY_NETWORK_TYPE[this.networkType],
|
|
96
|
-
...params,
|
|
97
|
-
},
|
|
98
|
-
{ arrayFormat: 'bracket' }
|
|
99
|
-
)
|
|
100
|
-
return `${GHOSTMARKET_URL_BY_NETWORK_TYPE[this.networkType]}/assets?${parameters}`
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
private parse(data: GhostMarketNFT) {
|
|
104
|
-
const nftResponse: NftResponse = {
|
|
105
|
-
collectionImage: this.treatGhostMarketImage(data.collection?.logoUrl),
|
|
106
|
-
id: data.tokenId,
|
|
107
|
-
contractHash: data.contract.hash,
|
|
108
|
-
symbol: data.contract.symbol,
|
|
109
|
-
collectionName: data.collection?.name,
|
|
110
|
-
image: this.treatGhostMarketImage(data.metadata.mediaUri),
|
|
111
|
-
isSVG: String(data.metadata.mediaType).includes('svg+xml'),
|
|
112
|
-
name: data.metadata.name,
|
|
113
|
-
creator: {
|
|
114
|
-
address: data.creator.address,
|
|
115
|
-
name: data.creator.offchainName,
|
|
116
|
-
},
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return nftResponse
|
|
120
|
-
}
|
|
121
|
-
}
|
package/src/LedgerServiceNeo3.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { LedgerService } from '@cityofzion/blockchain-service'
|
|
2
|
-
import Transport from '@ledgerhq/hw-transport'
|
|
3
|
-
import { wallet, api, u } from '@cityofzion/neon-js'
|
|
4
|
-
import { NeonParser } from '@cityofzion/neon-dappkit'
|
|
5
|
-
|
|
6
|
-
export class LedgerServiceNeo3 implements LedgerService {
|
|
7
|
-
async getAddress(transport: Transport): Promise<string> {
|
|
8
|
-
const publicKey = await this.getPublicKey(transport)
|
|
9
|
-
const { address } = new wallet.Account(publicKey)
|
|
10
|
-
|
|
11
|
-
return address
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
getSigningCallback(transport: Transport): api.SigningFunction {
|
|
15
|
-
return async (transaction, { witnessIndex, network }) => {
|
|
16
|
-
const publicKey = await this.getPublicKey(transport)
|
|
17
|
-
const account = new wallet.Account(publicKey)
|
|
18
|
-
|
|
19
|
-
const witnessScriptHash = wallet.getScriptHashFromVerificationScript(
|
|
20
|
-
transaction.witnesses[witnessIndex].verificationScript.toString()
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
if (account.scriptHash !== witnessScriptHash) {
|
|
24
|
-
throw new Error('Invalid witness script hash')
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const signature = await this.getSignature(transport, transaction.serialize(false), network, witnessIndex)
|
|
28
|
-
|
|
29
|
-
return signature
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async getSignature(transport: Transport, serializedTransaction: string, networkMagic: number, addressIndex = 0) {
|
|
34
|
-
const bip44Buffer = this.toBip44Buffer(addressIndex)
|
|
35
|
-
await transport.send(0x80, 0x02, 0, 0x80, bip44Buffer, [0x9000])
|
|
36
|
-
await transport.send(0x80, 0x02, 1, 0x80, Buffer.from(NeonParser.numToHex(networkMagic, 4, true), 'hex'), [0x9000])
|
|
37
|
-
|
|
38
|
-
const chunks = serializedTransaction.match(/.{1,510}/g) || []
|
|
39
|
-
|
|
40
|
-
for (let i = 0; i < chunks.length - 1; i++) {
|
|
41
|
-
await transport.send(0x80, 0x02, 2 + i, 0x80, Buffer.from(chunks[i], 'hex'), [0x9000])
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const response = await transport.send(
|
|
45
|
-
0x80,
|
|
46
|
-
0x02,
|
|
47
|
-
2 + chunks.length,
|
|
48
|
-
0x00,
|
|
49
|
-
Buffer.from(chunks[chunks.length - 1], 'hex'),
|
|
50
|
-
[0x9000]
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
if (response.length <= 2) {
|
|
54
|
-
throw new Error(`No more data but Ledger did not return signature!`)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return this.derSignatureToHex(response.toString('hex'))
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async getPublicKey(transport: Transport, addressIndex = 0): Promise<string> {
|
|
61
|
-
const bip44Buffer = this.toBip44Buffer(addressIndex)
|
|
62
|
-
|
|
63
|
-
const result = await transport.send(0x80, 0x04, 0x00, 0x00, bip44Buffer, [0x9000])
|
|
64
|
-
const publicKey = result.toString('hex').substring(0, 130)
|
|
65
|
-
|
|
66
|
-
return publicKey
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
private toBip44Buffer(addressIndex = 0, changeIndex = 0, accountIndex = 0) {
|
|
70
|
-
const accountHex = this.to8BitHex(accountIndex + 0x80000000)
|
|
71
|
-
const changeHex = this.to8BitHex(changeIndex)
|
|
72
|
-
const addressHex = this.to8BitHex(addressIndex)
|
|
73
|
-
|
|
74
|
-
return Buffer.from('8000002C' + '80000378' + accountHex + changeHex + addressHex, 'hex')
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
private to8BitHex(num: number): string {
|
|
78
|
-
const hex = num.toString(16)
|
|
79
|
-
return '0'.repeat(8 - hex.length) + hex
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
private derSignatureToHex(response: string): string {
|
|
83
|
-
const ss = new u.StringStream(response)
|
|
84
|
-
// The first byte is format. It is usually 0x30 (SEQ) or 0x31 (SET)
|
|
85
|
-
// The second byte represents the total length of the DER module.
|
|
86
|
-
ss.read(2)
|
|
87
|
-
// Now we read each field off
|
|
88
|
-
// Each field is encoded with a type byte, length byte followed by the data itself
|
|
89
|
-
ss.read(1) // Read and drop the type
|
|
90
|
-
const r = ss.readVarBytes()
|
|
91
|
-
ss.read(1)
|
|
92
|
-
const s = ss.readVarBytes()
|
|
93
|
-
|
|
94
|
-
// We will need to ensure both integers are 32 bytes long
|
|
95
|
-
const integers = [r, s].map(i => {
|
|
96
|
-
if (i.length < 64) {
|
|
97
|
-
i = '0'.repeat(i.length - 64) + i
|
|
98
|
-
}
|
|
99
|
-
if (i.length > 64) {
|
|
100
|
-
i = i.substr(-64)
|
|
101
|
-
}
|
|
102
|
-
return i
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
return integers.join('')
|
|
106
|
-
}
|
|
107
|
-
}
|
package/src/RpcBDSNeo3.ts
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BDSClaimable,
|
|
3
|
-
BalanceResponse,
|
|
4
|
-
BlockchainDataService,
|
|
5
|
-
ContractMethod,
|
|
6
|
-
ContractParameter,
|
|
7
|
-
ContractResponse,
|
|
8
|
-
Network,
|
|
9
|
-
Token,
|
|
10
|
-
TransactionResponse,
|
|
11
|
-
TransactionsByAddressParams,
|
|
12
|
-
TransactionsByAddressResponse,
|
|
13
|
-
} from '@cityofzion/blockchain-service'
|
|
14
|
-
import { rpc, u } from '@cityofzion/neon-core'
|
|
15
|
-
import { NeonInvoker, TypeChecker } from '@cityofzion/neon-dappkit'
|
|
16
|
-
import { TOKENS } from './constants'
|
|
17
|
-
|
|
18
|
-
export class RPCBDSNeo3 implements BlockchainDataService, BDSClaimable {
|
|
19
|
-
protected readonly tokenCache: Map<string, Token> = new Map()
|
|
20
|
-
protected readonly feeToken: Token
|
|
21
|
-
protected readonly claimToken: Token
|
|
22
|
-
readonly network: Network
|
|
23
|
-
|
|
24
|
-
maxTimeToConfirmTransactionInMs: number = 1000 * 60 * 2
|
|
25
|
-
|
|
26
|
-
constructor(network: Network, feeToken: Token, claimToken: Token) {
|
|
27
|
-
this.network = network
|
|
28
|
-
this.feeToken = feeToken
|
|
29
|
-
this.claimToken = claimToken
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async getTransaction(hash: string): Promise<TransactionResponse> {
|
|
33
|
-
try {
|
|
34
|
-
const rpcClient = new rpc.RPCClient(this.network.url)
|
|
35
|
-
const response = await rpcClient.getRawTransaction(hash, true)
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
hash: response.hash,
|
|
39
|
-
block: response.validuntilblock,
|
|
40
|
-
fee: u.BigInteger.fromNumber(response.netfee ?? 0)
|
|
41
|
-
.add(u.BigInteger.fromNumber(response.sysfee ?? 0))
|
|
42
|
-
.toDecimal(this.feeToken.decimals),
|
|
43
|
-
notifications: [],
|
|
44
|
-
transfers: [],
|
|
45
|
-
time: response.blocktime,
|
|
46
|
-
}
|
|
47
|
-
} catch {
|
|
48
|
-
throw new Error(`Transaction not found: ${hash}`)
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async getTransactionsByAddress(_params: TransactionsByAddressParams): Promise<TransactionsByAddressResponse> {
|
|
53
|
-
throw new Error('Method not supported.')
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async getContract(contractHash: string): Promise<ContractResponse> {
|
|
57
|
-
try {
|
|
58
|
-
const rpcClient = new rpc.RPCClient(this.network.url)
|
|
59
|
-
const contractState = await rpcClient.getContractState(contractHash)
|
|
60
|
-
|
|
61
|
-
const methods = contractState.manifest.abi.methods.map<ContractMethod>(method => ({
|
|
62
|
-
name: method.name,
|
|
63
|
-
parameters: method.parameters.map<ContractParameter>(parameter => ({
|
|
64
|
-
name: parameter.name,
|
|
65
|
-
type: parameter.type,
|
|
66
|
-
})),
|
|
67
|
-
}))
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
hash: contractState.hash,
|
|
71
|
-
name: contractState.manifest.name,
|
|
72
|
-
methods,
|
|
73
|
-
}
|
|
74
|
-
} catch {
|
|
75
|
-
throw new Error(`Contract not found: ${contractHash}`)
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async getTokenInfo(tokenHash: string): Promise<Token> {
|
|
80
|
-
const localToken = TOKENS[this.network.type].find(token => token.hash === tokenHash)
|
|
81
|
-
if (localToken) return localToken
|
|
82
|
-
|
|
83
|
-
if (this.tokenCache.has(tokenHash)) {
|
|
84
|
-
return this.tokenCache.get(tokenHash)!
|
|
85
|
-
}
|
|
86
|
-
try {
|
|
87
|
-
const rpcClient = new rpc.RPCClient(this.network.url)
|
|
88
|
-
const contractState = await rpcClient.getContractState(tokenHash)
|
|
89
|
-
|
|
90
|
-
const invoker = await NeonInvoker.init({
|
|
91
|
-
rpcAddress: this.network.url,
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
const response = await invoker.testInvoke({
|
|
95
|
-
invocations: [
|
|
96
|
-
{
|
|
97
|
-
scriptHash: tokenHash,
|
|
98
|
-
operation: 'decimals',
|
|
99
|
-
args: [],
|
|
100
|
-
},
|
|
101
|
-
{ scriptHash: tokenHash, operation: 'symbol', args: [] },
|
|
102
|
-
],
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
if (!TypeChecker.isStackTypeInteger(response.stack[0])) throw new Error('Invalid decimals')
|
|
106
|
-
if (!TypeChecker.isStackTypeByteString(response.stack[1])) throw new Error('Invalid symbol')
|
|
107
|
-
const decimals = Number(response.stack[0].value)
|
|
108
|
-
const symbol = u.base642utf8(response.stack[1].value)
|
|
109
|
-
const token = {
|
|
110
|
-
name: contractState.manifest.name,
|
|
111
|
-
symbol,
|
|
112
|
-
hash: contractState.hash,
|
|
113
|
-
decimals,
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
this.tokenCache.set(tokenHash, token)
|
|
117
|
-
|
|
118
|
-
return token
|
|
119
|
-
} catch {
|
|
120
|
-
throw new Error(`Token not found: ${tokenHash}`)
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async getBalance(address: string): Promise<BalanceResponse[]> {
|
|
125
|
-
const rpcClient = new rpc.RPCClient(this.network.url)
|
|
126
|
-
const response = await rpcClient.getNep17Balances(address)
|
|
127
|
-
|
|
128
|
-
const promises = response.balance.map<Promise<BalanceResponse>>(async balance => {
|
|
129
|
-
let token: Token = {
|
|
130
|
-
hash: balance.assethash,
|
|
131
|
-
name: '-',
|
|
132
|
-
symbol: '-',
|
|
133
|
-
decimals: 8,
|
|
134
|
-
}
|
|
135
|
-
try {
|
|
136
|
-
token = await this.getTokenInfo(balance.assethash)
|
|
137
|
-
} catch {
|
|
138
|
-
// Empty Block
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return {
|
|
142
|
-
amount: u.BigInteger.fromNumber(balance.amount).toDecimal(token?.decimals ?? 8),
|
|
143
|
-
token,
|
|
144
|
-
}
|
|
145
|
-
})
|
|
146
|
-
const balances = await Promise.all(promises)
|
|
147
|
-
|
|
148
|
-
return balances
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
async getBlockHeight(): Promise<number> {
|
|
152
|
-
const rpcClient = new rpc.RPCClient(this.network.url)
|
|
153
|
-
return await rpcClient.getBlockCount()
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
async getUnclaimed(address: string): Promise<string> {
|
|
157
|
-
const rpcClient = new rpc.RPCClient(this.network.url)
|
|
158
|
-
const response = await rpcClient.getUnclaimedGas(address)
|
|
159
|
-
return u.BigInteger.fromNumber(response).toDecimal(this.claimToken.decimals)
|
|
160
|
-
}
|
|
161
|
-
}
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { BDSClaimable, BlockchainDataService } from '@cityofzion/blockchain-service'
|
|
2
|
-
import { DoraBDSNeo3 } from '../DoraBDSNeo3'
|
|
3
|
-
import { RPCBDSNeo3 } from '../RpcBDSNeo3'
|
|
4
|
-
import { DEFAULT_URL_BY_NETWORK_TYPE, TOKENS } from '../constants'
|
|
5
|
-
|
|
6
|
-
const gasToken = TOKENS.testnet.find(t => t.symbol === 'GAS')!
|
|
7
|
-
const doraBDSNeo3 = new DoraBDSNeo3({ type: 'testnet', url: DEFAULT_URL_BY_NETWORK_TYPE.testnet }, gasToken, gasToken)
|
|
8
|
-
const rpcBDSNeo3 = new RPCBDSNeo3({ type: 'testnet', url: DEFAULT_URL_BY_NETWORK_TYPE.testnet }, gasToken, gasToken)
|
|
9
|
-
|
|
10
|
-
describe('BDSNeo3', () => {
|
|
11
|
-
it.each([doraBDSNeo3, rpcBDSNeo3])(
|
|
12
|
-
'Should be able to get transaction - %s',
|
|
13
|
-
async (bdsNeo3: BlockchainDataService) => {
|
|
14
|
-
const hash = '0x70e7381c5dee6e81becd02844e4e0199f6b3df834213bc89418dc4da32cf3f21'
|
|
15
|
-
const transaction = await bdsNeo3.getTransaction(hash)
|
|
16
|
-
|
|
17
|
-
expect(transaction).toEqual(
|
|
18
|
-
expect.objectContaining({
|
|
19
|
-
block: expect.any(Number),
|
|
20
|
-
hash,
|
|
21
|
-
notifications: [],
|
|
22
|
-
transfers: [],
|
|
23
|
-
time: expect.any(Number),
|
|
24
|
-
fee: expect.any(String),
|
|
25
|
-
})
|
|
26
|
-
)
|
|
27
|
-
}
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
it.each([doraBDSNeo3])(
|
|
31
|
-
'Should be able to get transactions of address - %s',
|
|
32
|
-
async (bdsNeo3: BlockchainDataService) => {
|
|
33
|
-
const address = 'NNmTVFrSPhe7zjgN6iq9cLgXJwLZziUKV6'
|
|
34
|
-
const response = await bdsNeo3.getTransactionsByAddress({ address, page: 1 })
|
|
35
|
-
|
|
36
|
-
response.transactions.forEach(transaction => {
|
|
37
|
-
expect(transaction).toEqual(
|
|
38
|
-
expect.objectContaining({
|
|
39
|
-
block: expect.any(Number),
|
|
40
|
-
hash: expect.any(String),
|
|
41
|
-
time: expect.any(Number),
|
|
42
|
-
fee: expect.any(String),
|
|
43
|
-
notifications: expect.arrayContaining([
|
|
44
|
-
expect.objectContaining({
|
|
45
|
-
eventName: expect.any(String),
|
|
46
|
-
state: expect.arrayContaining([
|
|
47
|
-
{
|
|
48
|
-
type: expect.any(String),
|
|
49
|
-
value: expect.any(String),
|
|
50
|
-
},
|
|
51
|
-
]),
|
|
52
|
-
}),
|
|
53
|
-
]),
|
|
54
|
-
transfers: expect.arrayContaining([
|
|
55
|
-
expect.objectContaining({
|
|
56
|
-
amount: expect.any(String),
|
|
57
|
-
from: expect.any(String),
|
|
58
|
-
to: expect.any(String),
|
|
59
|
-
type: 'token',
|
|
60
|
-
}),
|
|
61
|
-
]),
|
|
62
|
-
})
|
|
63
|
-
)
|
|
64
|
-
})
|
|
65
|
-
}
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
it.each([doraBDSNeo3, rpcBDSNeo3])('Should be able to get contract - %s', async (bdsNeo3: BlockchainDataService) => {
|
|
69
|
-
const hash = '0xd2a4cff31913016155e38e474a2c06d08be276cf'
|
|
70
|
-
const contract = await bdsNeo3.getContract(hash)
|
|
71
|
-
expect(contract).toEqual({
|
|
72
|
-
hash: hash,
|
|
73
|
-
name: 'GasToken',
|
|
74
|
-
methods: expect.arrayContaining([
|
|
75
|
-
expect.objectContaining({
|
|
76
|
-
name: expect.any(String),
|
|
77
|
-
parameters: expect.arrayContaining([
|
|
78
|
-
expect.objectContaining({ name: expect.any(String), type: expect.any(String) }),
|
|
79
|
-
]),
|
|
80
|
-
}),
|
|
81
|
-
]),
|
|
82
|
-
})
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
it.each([doraBDSNeo3, rpcBDSNeo3])(
|
|
86
|
-
'Should be able to get token info - %s',
|
|
87
|
-
async (bdsNeo3: BlockchainDataService) => {
|
|
88
|
-
const hash = '0xd2a4cff31913016155e38e474a2c06d08be276cf'
|
|
89
|
-
const token = await bdsNeo3.getTokenInfo(hash)
|
|
90
|
-
|
|
91
|
-
expect(token).toEqual({
|
|
92
|
-
decimals: 8,
|
|
93
|
-
hash: hash,
|
|
94
|
-
name: 'GasToken',
|
|
95
|
-
symbol: 'GAS',
|
|
96
|
-
})
|
|
97
|
-
}
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
it.each([doraBDSNeo3, rpcBDSNeo3])('Should be able to get balance - %s', async (bdsNeo3: BlockchainDataService) => {
|
|
101
|
-
const address = 'NNmTVFrSPhe7zjgN6iq9cLgXJwLZziUKV6'
|
|
102
|
-
const balance = await bdsNeo3.getBalance(address)
|
|
103
|
-
balance.forEach(balance => {
|
|
104
|
-
expect(balance).toEqual({
|
|
105
|
-
amount: expect.any(String),
|
|
106
|
-
token: {
|
|
107
|
-
hash: expect.any(String),
|
|
108
|
-
name: expect.any(String),
|
|
109
|
-
symbol: expect.any(String),
|
|
110
|
-
decimals: expect.any(Number),
|
|
111
|
-
},
|
|
112
|
-
})
|
|
113
|
-
})
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
it.each([doraBDSNeo3, rpcBDSNeo3])(
|
|
117
|
-
'Should be able to get unclaimed - %s',
|
|
118
|
-
async (bdsNeo3: BlockchainDataService & BDSClaimable) => {
|
|
119
|
-
const address = 'NNmTVFrSPhe7zjgN6iq9cLgXJwLZziUKV6'
|
|
120
|
-
const unclaimed = await bdsNeo3.getUnclaimed(address)
|
|
121
|
-
expect(unclaimed).toEqual(expect.any(String))
|
|
122
|
-
}
|
|
123
|
-
)
|
|
124
|
-
})
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import { sleep } from './utils/sleep'
|
|
2
|
-
import { BSNeo3 } from '../BSNeo3'
|
|
3
|
-
import { generateMnemonic } from '@cityofzion/bs-asteroid-sdk'
|
|
4
|
-
import TransportNodeHid from '@ledgerhq/hw-transport-node-hid'
|
|
5
|
-
|
|
6
|
-
let bsNeo3: BSNeo3
|
|
7
|
-
|
|
8
|
-
describe('BSNeo3', () => {
|
|
9
|
-
beforeAll(() => {
|
|
10
|
-
bsNeo3 = new BSNeo3('neo3', { type: 'testnet', url: 'https://testnet1.neo.coz.io:443' })
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
it('Should be able to validate an address', () => {
|
|
14
|
-
const validAddress = 'NPRMF5bmYuW23DeDJqsDJenhXkAPSJyuYe'
|
|
15
|
-
const invalidAddress = 'invalid address'
|
|
16
|
-
|
|
17
|
-
expect(bsNeo3.validateAddress(validAddress)).toBeTruthy()
|
|
18
|
-
expect(bsNeo3.validateAddress(invalidAddress)).toBeFalsy()
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it('Should be able to validate an encrypted key', () => {
|
|
22
|
-
const validEncryptedKey = '6PYVPVe1fQznphjbUxXP9KZJqPMVnVwCx5s5pr5axRJ8uHkMtZg97eT5kL'
|
|
23
|
-
const invalidEncryptedKey = 'invalid encrypted key'
|
|
24
|
-
|
|
25
|
-
expect(bsNeo3.validateEncrypted(validEncryptedKey)).toBeTruthy()
|
|
26
|
-
expect(bsNeo3.validateEncrypted(invalidEncryptedKey)).toBeFalsy()
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it('Should be able to validate a wif', () => {
|
|
30
|
-
const validWif = 'L44B5gGEpqEDRS9vVPz7QT35jcBG2r3CZwSwQ4fCewXAhAhqGVpP'
|
|
31
|
-
const invalidWif = 'invalid wif'
|
|
32
|
-
|
|
33
|
-
expect(bsNeo3.validateKey(validWif)).toBeTruthy()
|
|
34
|
-
expect(bsNeo3.validateKey(invalidWif)).toBeFalsy()
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
it('Should be able to validate an domain', () => {
|
|
38
|
-
const validDomain = 'test.neo'
|
|
39
|
-
const invalidDomain = 'invalid domain'
|
|
40
|
-
|
|
41
|
-
expect(bsNeo3.validateNameServiceDomainFormat(validDomain)).toBeTruthy()
|
|
42
|
-
expect(bsNeo3.validateNameServiceDomainFormat(invalidDomain)).toBeFalsy()
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it('Should be able to generate a mnemonic', () => {
|
|
46
|
-
expect(() => {
|
|
47
|
-
const mnemonic = generateMnemonic()
|
|
48
|
-
expect(mnemonic).toHaveLength(12)
|
|
49
|
-
}).not.toThrowError()
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
it('Should be able to gererate a account from mnemonic', () => {
|
|
53
|
-
const mnemonic = generateMnemonic()
|
|
54
|
-
const account = bsNeo3.generateAccountFromMnemonic(mnemonic, 0)
|
|
55
|
-
|
|
56
|
-
expect(bsNeo3.validateAddress(account.address)).toBeTruthy()
|
|
57
|
-
expect(bsNeo3.validateKey(account.key)).toBeTruthy()
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it('Should be able to generate a account from wif', () => {
|
|
61
|
-
const mnemonic = generateMnemonic()
|
|
62
|
-
const account = bsNeo3.generateAccountFromMnemonic(mnemonic, 0)
|
|
63
|
-
|
|
64
|
-
const accountFromWif = bsNeo3.generateAccountFromKey(account.key)
|
|
65
|
-
expect(account).toEqual(expect.objectContaining(accountFromWif))
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
it('Should be able to decrypt a encrypted key', async () => {
|
|
69
|
-
const mnemonic = generateMnemonic()
|
|
70
|
-
const account = bsNeo3.generateAccountFromMnemonic(mnemonic, 0)
|
|
71
|
-
const password = 'TestPassword'
|
|
72
|
-
const encryptedKey = await bsNeo3.encrypt(account.key, password)
|
|
73
|
-
const decryptedAccount = await bsNeo3.decrypt(encryptedKey, password)
|
|
74
|
-
expect(account).toEqual(expect.objectContaining(decryptedAccount))
|
|
75
|
-
}, 20000)
|
|
76
|
-
|
|
77
|
-
it('Should be able to encrypt a key', async () => {
|
|
78
|
-
const mnemonic = generateMnemonic()
|
|
79
|
-
const account = bsNeo3.generateAccountFromMnemonic(mnemonic, 0)
|
|
80
|
-
const password = 'TestPassword'
|
|
81
|
-
const encryptedKey = await bsNeo3.encrypt(account.key, password)
|
|
82
|
-
expect(encryptedKey).toEqual(expect.any(String))
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
it.skip('Should be able to calculate transfer fee', async () => {
|
|
86
|
-
const account = bsNeo3.generateAccountFromKey(process.env.TESTNET_PRIVATE_KEY as string)
|
|
87
|
-
|
|
88
|
-
const fee = await bsNeo3.calculateTransferFee({
|
|
89
|
-
senderAccount: account,
|
|
90
|
-
intent: {
|
|
91
|
-
amount: '0.00000001',
|
|
92
|
-
receiverAddress: 'NPRMF5bmYuW23DeDJqsDJenhXkAPSJyuYe',
|
|
93
|
-
tokenHash: bsNeo3.feeToken.hash,
|
|
94
|
-
tokenDecimals: bsNeo3.feeToken.decimals,
|
|
95
|
-
},
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
expect(fee).toEqual({
|
|
99
|
-
total: expect.any(Number),
|
|
100
|
-
networkFee: expect.any(Number),
|
|
101
|
-
systemFee: expect.any(Number),
|
|
102
|
-
})
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
it.skip('Should be able to transfer', async () => {
|
|
106
|
-
const account = bsNeo3.generateAccountFromKey(process.env.TESTNET_PRIVATE_KEY as string)
|
|
107
|
-
const balance = await bsNeo3.blockchainDataService.getBalance(account.address)
|
|
108
|
-
const gasBalance = balance.find(b => b.token.symbol === bsNeo3.feeToken.symbol)
|
|
109
|
-
expect(Number(gasBalance?.amount)).toBeGreaterThan(0.00000001)
|
|
110
|
-
|
|
111
|
-
const transactionHash = await bsNeo3.transfer({
|
|
112
|
-
senderAccount: account,
|
|
113
|
-
intent: {
|
|
114
|
-
amount: '0.00000001',
|
|
115
|
-
receiverAddress: 'NPRMF5bmYuW23DeDJqsDJenhXkAPSJyuYe',
|
|
116
|
-
tokenHash: bsNeo3.feeToken.hash,
|
|
117
|
-
tokenDecimals: bsNeo3.feeToken.decimals,
|
|
118
|
-
},
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
expect(transactionHash).toEqual(expect.any(String))
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
it.skip('Should be able to transfer with ledger', async () => {
|
|
125
|
-
const transport = await TransportNodeHid.create()
|
|
126
|
-
const publicKey = await bsNeo3.ledgerService.getPublicKey(transport)
|
|
127
|
-
|
|
128
|
-
const account = bsNeo3.generateAccountFromPublicKey(publicKey)
|
|
129
|
-
|
|
130
|
-
const balance = await bsNeo3.blockchainDataService.getBalance(account.address)
|
|
131
|
-
const gasBalance = balance.find(b => b.token.symbol === bsNeo3.feeToken.symbol)
|
|
132
|
-
expect(Number(gasBalance?.amount)).toBeGreaterThan(0.00000001)
|
|
133
|
-
|
|
134
|
-
const transactionHash = await bsNeo3.transfer({
|
|
135
|
-
senderAccount: account,
|
|
136
|
-
intent: {
|
|
137
|
-
amount: '1',
|
|
138
|
-
receiverAddress: 'NPRMF5bmYuW23DeDJqsDJenhXkAPSJyuYe',
|
|
139
|
-
tokenHash: bsNeo3.feeToken.hash,
|
|
140
|
-
tokenDecimals: bsNeo3.feeToken.decimals,
|
|
141
|
-
},
|
|
142
|
-
isLedger: true,
|
|
143
|
-
ledgerTransport: transport,
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
expect(transactionHash).toEqual(expect.any(String))
|
|
147
|
-
}, 60000)
|
|
148
|
-
|
|
149
|
-
it.skip('Should be able to claim', async () => {
|
|
150
|
-
const account = bsNeo3.generateAccountFromKey(process.env.TESTNET_PRIVATE_KEY as string)
|
|
151
|
-
|
|
152
|
-
const maxTries = 10
|
|
153
|
-
let tries = 0
|
|
154
|
-
|
|
155
|
-
while (tries < maxTries) {
|
|
156
|
-
try {
|
|
157
|
-
const unclaimed = await bsNeo3.blockchainDataService.getUnclaimed(account.address)
|
|
158
|
-
if (Number(unclaimed) <= 0) continue
|
|
159
|
-
|
|
160
|
-
const transactionHash = await bsNeo3.claim(account)
|
|
161
|
-
expect(transactionHash).toEqual(expect.any(String))
|
|
162
|
-
break
|
|
163
|
-
} catch (error) {
|
|
164
|
-
await sleep(4000)
|
|
165
|
-
} finally {
|
|
166
|
-
tries++
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}, 60000)
|
|
170
|
-
|
|
171
|
-
it('Should be able to resolve a name service domain', async () => {
|
|
172
|
-
const owner = await bsNeo3.resolveNameServiceDomain('neo.neo')
|
|
173
|
-
expect(owner).toEqual('Nj39M97Rk2e23JiULBBMQmvpcnKaRHqxFf')
|
|
174
|
-
})
|
|
175
|
-
})
|