@stellar-expert/tx-meta-effects-parser 5.0.0-beta10

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.
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ presets: [['@babel/preset-env', {targets: {node: 'current'}}]]
3
+ }
package/jest.config.js ADDED
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ clearMocks: true,
3
+ transformIgnorePatterns: [],
4
+ maxWorkers: 1
5
+ //runner: "jest-runner",
6
+ }
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@stellar-expert/tx-meta-effects-parser",
3
+ "version": "5.0.0-beta10",
4
+ "description": "Low-level effects parser for Stellar transaction results and meta XDR",
5
+ "main": "src/index.js",
6
+ "scripts": {
7
+ "test": "jest"
8
+ },
9
+ "author": "team@stellar.expert",
10
+ "license": "MIT",
11
+ "peerDependencies": {
12
+ "@stellar/stellar-base": "^10.0.1"
13
+ },
14
+ "devDependencies": {
15
+ "@babel/core": "^7.22.9",
16
+ "@babel/preset-env": "^7.22.9",
17
+ "@stellar-expert/eslint-config-js": "^1.1.0",
18
+ "babel-jest": "^29.6.1",
19
+ "jest": "^29.6.1"
20
+ }
21
+ }
@@ -0,0 +1,150 @@
1
+ const {StrKey} = require('@stellar/stellar-base')
2
+ const effectTypes = require('./effect-types')
3
+ const {UnexpectedTxMetaChangeError} = require('./errors')
4
+
5
+ /**
6
+ * Returns true for AlphaNum4/12 assets adn false otherwise
7
+ * @param {String} asset
8
+ */
9
+ function isAsset(asset) {
10
+ return asset.includes('-') //lazy check for {code}-{issuer}-{type} format
11
+ }
12
+
13
+
14
+ /**
15
+ * Convert value in stroops (Int64 amount) to the normal string representation
16
+ * @param {String|Number|BigInt} valueInStroops
17
+ * @return {String}
18
+ */
19
+ function fromStroops(valueInStroops) {
20
+ try {
21
+ let parsed = typeof valueInStroops === 'bigint' ?
22
+ valueInStroops :
23
+ BigInt(valueInStroops.toString())
24
+ let negative = false
25
+ if (parsed < 0n) {
26
+ negative = true
27
+ parsed *= -1n
28
+ }
29
+ const int = parsed / 10000000n
30
+ const fract = parsed % 10000000n
31
+ let res = int.toString()
32
+ if (fract) {
33
+ res += '.' + fract.toString().padStart(7, '0')
34
+ }
35
+ if (negative) {
36
+ res = '-' + res
37
+ }
38
+ return trimZeros(res)
39
+ } catch (e) {
40
+ return '0'
41
+ }
42
+ }
43
+
44
+
45
+ /**
46
+ * Convert arbitrary stringified amount to int64 representation
47
+ * @param {String|Number} value
48
+ * @return {BigInt}
49
+ */
50
+ function toStroops(value) {
51
+ if (!value)
52
+ return 0n
53
+ if (typeof value === 'number') {
54
+ value = value.toFixed(7)
55
+ }
56
+ if (typeof value !== 'string' || !/^-?[\d.,]+$/.test(value))
57
+ return 0n //invalid format
58
+ try {
59
+ let [int, decimal = '0'] = value.split('.', 2)
60
+ let negative = false
61
+ if (int.startsWith('-')) {
62
+ negative = true
63
+ int = int.slice(1)
64
+ }
65
+ let res = BigInt(int) * 10000000n + BigInt(decimal.slice(0, 7).padEnd(7, '0'))
66
+ if (negative) {
67
+ res *= -1n
68
+ if (res < -0x8000000000000000n) //overflow
69
+ return 0n
70
+ } else if (res > 0xFFFFFFFFFFFFFFFFn) //overflow
71
+ return 0n
72
+ return res
73
+ } catch (e) {
74
+ return 0n
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Trim trailing fractional zeros from a string amount representation
80
+ * @param {String} value
81
+ * @return {String}
82
+ * @internal
83
+ */
84
+ function trimZeros(value) {
85
+ const [int, fract] = value.split('.')
86
+ if (!fract)
87
+ return int
88
+ const trimmed = fract.replace(/0+$/, '')
89
+ if (!trimmed.length)
90
+ return int
91
+ return int + '.' + trimmed
92
+ }
93
+
94
+ /**
95
+ * Replace multiplexed addresses with base G addresses
96
+ * @param {String} address
97
+ * @return {String}
98
+ * @internal
99
+ */
100
+ function normalizeAddress(address) {
101
+ const prefix = address[0]
102
+ if (prefix === 'G')
103
+ return address //lazy check for ed25519 G address
104
+ if (prefix !== 'M')
105
+ throw new TypeError('Expected ED25519 or Muxed address')
106
+ const rawBytes = StrKey.decodeMed25519PublicKey(address)
107
+ return StrKey.encodeEd25519PublicKey(rawBytes.subarray(0, 32))
108
+ }
109
+
110
+ /**
111
+ * @param {String} action
112
+ * @param {String} type
113
+ * @return {String}
114
+ */
115
+ function encodeSponsorshipEffectName(action, type) {
116
+ let actionKey
117
+ switch (action) {
118
+ case 'created':
119
+ actionKey = 'Created'
120
+ break
121
+ case 'updated':
122
+ actionKey = 'Updated'
123
+ break
124
+ case 'removed':
125
+ actionKey = 'Removed'
126
+ break
127
+ default:
128
+ throw new UnexpectedTxMetaChangeError({action, type})
129
+ }
130
+ return effectTypes[`${type}Sponsorship${actionKey}`]
131
+ }
132
+
133
+ /**
134
+ * Check if asset issuer is a source account
135
+ * @param {String} account
136
+ * @param {String} asset
137
+ * @return {Boolean}
138
+ */
139
+ function isIssuer(account, asset) {
140
+ return asset.includes(account)
141
+ }
142
+
143
+ module.exports = {
144
+ fromStroops,
145
+ toStroops,
146
+ normalizeAddress,
147
+ encodeSponsorshipEffectName,
148
+ isIssuer,
149
+ isAsset
150
+ }
@@ -0,0 +1,130 @@
1
+ const effectTypes = require('./effect-types')
2
+
3
+ /**
4
+ * Effect supply computation processor
5
+ */
6
+ class AssetSupplyProcessor {
7
+ constructor(effects) {
8
+ this.assetTransfers = {}
9
+ this.processXlmBalances = effects.some(e => e.type === 'contractInvoked')
10
+ for (const effect of effects) {
11
+ this.processEffect(effect)
12
+ }
13
+ }
14
+
15
+ /**
16
+ * @type {Object.<String,BigInt>}
17
+ * @private
18
+ */
19
+ assetTransfers
20
+
21
+ /**
22
+ * @type {Boolean}
23
+ * @private
24
+ */
25
+ processXlmBalances = false
26
+
27
+ /**
28
+ * Process generated operation effect
29
+ * @param {{}} effect
30
+ */
31
+ processEffect(effect) {
32
+ switch (effect.type) {
33
+ case effectTypes.accountCredited:
34
+ case effectTypes.claimableBalanceCreated:
35
+ case effectTypes.assetBurned:
36
+ //increase supply
37
+ this.increase(effect.asset, effect.amount)
38
+ break
39
+ case effectTypes.accountDebited:
40
+ case effectTypes.claimableBalanceRemoved:
41
+ case effectTypes.assetMinted:
42
+ //decrease supply
43
+ this.decrease(effect.asset, effect.amount)
44
+ break
45
+ case effectTypes.liquidityPoolDeposited:
46
+ //increase supply for every deposited asset (if liquidity provider is an issuer)
47
+ for (const {asset, amount} of effect.assets) {
48
+ this.increase(asset, amount)
49
+ }
50
+ break
51
+ case effectTypes.liquidityPoolWithdrew:
52
+ //decrease supply for every deposited asset (if liquidity provider is an issuer)
53
+ for (const {asset, amount} of effect.assets) {
54
+ this.decrease(asset, amount)
55
+ }
56
+ break
57
+ case effectTypes.trade:
58
+ if (effect.pool) {
59
+ for (let i = 0; i < effect.asset.length; i++) {
60
+ if (i === 0) { //increase supply if the issuer is seller
61
+ this.decrease(effect.asset[i], effect.amount[i])
62
+ } else {
63
+ this.increase(effect.asset[i], effect.amount[i])
64
+ }
65
+ }
66
+ }
67
+ break
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Calculate differences and generate minted/burned effects if needed
73
+ * @return {{}[]}
74
+ */
75
+ resolve() {
76
+ const res = []
77
+ for (const [asset, amount] of Object.entries(this.assetTransfers)) {
78
+ if (amount === 0n)
79
+ continue
80
+ const effect = {
81
+ type: effectTypes.assetMinted,
82
+ asset
83
+ }
84
+ if (amount > 0n) {
85
+ effect.type = effectTypes.assetMinted
86
+ effect.amount = amount.toString()
87
+ }
88
+ if (amount < 0n) {
89
+ effect.type = effectTypes.assetBurned
90
+ effect.amount = (-amount).toString()
91
+ }
92
+ res.push(effect)
93
+ }
94
+ return res
95
+ }
96
+
97
+ /**
98
+ * @param {String} asset
99
+ * @param {String} amount
100
+ * @private
101
+ */
102
+ increase(asset, amount) {
103
+ if (!this.shouldProcessAsset(asset))
104
+ return
105
+ this.assetTransfers[asset] = (this.assetTransfers[asset] || 0n) + BigInt(amount)
106
+ }
107
+
108
+ /**
109
+ * @param {String} asset
110
+ * @param {String} amount
111
+ * @private
112
+ */
113
+ decrease(asset, amount) {
114
+ if (!this.shouldProcessAsset(asset))
115
+ return
116
+ this.assetTransfers[asset] = (this.assetTransfers[asset] || 0n) - BigInt(amount)
117
+ }
118
+
119
+ /**
120
+ * @param {String} asset
121
+ * @return {Boolean}
122
+ */
123
+ shouldProcessAsset(asset) {
124
+ if (asset === 'XLM') //return true if we process XLM balance changes
125
+ return this.processXlmBalances
126
+ return asset.includes('-') || (asset.length === 56 && asset.startsWith('C')) //lazy checks for alphanum4/12 assets and contracts
127
+ }
128
+ }
129
+
130
+ module.exports = AssetSupplyProcessor
@@ -0,0 +1,43 @@
1
+ const {Asset, StrKey, hash, xdr} = require('@stellar/stellar-base')
2
+
3
+ const passphraseMapping = {}
4
+
5
+ /**
6
+ * Resolve network id hash from a passphrase (with pre-caching)
7
+ * @param {String} networkPassphrase
8
+ * @return {Buffer}
9
+ */
10
+ function getNetworkIdHash(networkPassphrase) {
11
+ let networkId = passphraseMapping[networkPassphrase]
12
+ if (!networkId) {
13
+ networkId = passphraseMapping[networkPassphrase] = hash(Buffer.from(networkPassphrase))
14
+ }
15
+ return networkId
16
+ }
17
+
18
+ /**
19
+ * Derive ContractId from a wrapped Stellar classic asset
20
+ * @param {Asset} asset
21
+ * @param {String} networkPassphrase
22
+ * @return {String}
23
+ */
24
+ function contractIdFromAsset(asset, networkPassphrase) {
25
+ return contractIdFromPreimage(xdr.ContractIdPreimage.contractIdPreimageFromAsset(asset.toXDRObject()), networkPassphrase)
26
+ }
27
+
28
+ /**
29
+ * Derive ContractId from a hash preimage
30
+ * @param {ContractIdPreimage} contractIdPreimage
31
+ * @param {String} networkPassphrase
32
+ * @return {String}
33
+ */
34
+ function contractIdFromPreimage(contractIdPreimage, networkPassphrase) {
35
+ const hashPreimage = new xdr.HashIdPreimageContractId({
36
+ networkId: getNetworkIdHash(networkPassphrase),
37
+ contractIdPreimage
38
+ })
39
+ const envelopePreimage = xdr.HashIdPreimage.envelopeTypeContractId(hashPreimage)
40
+ return StrKey.encodeContract(hash(envelopePreimage.toXDR()))
41
+ }
42
+
43
+ module.exports = {contractIdFromAsset, contractIdFromPreimage}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * All supported effects types
3
+ * @readonly
4
+ */
5
+ const effectTypes = {
6
+ feeCharged: 'feeCharged',
7
+
8
+ accountCreated: 'accountCreated',
9
+ accountRemoved: 'accountRemoved',
10
+
11
+ accountDebited: 'accountDebited',
12
+ accountCredited: 'accountCredited',
13
+
14
+ accountHomeDomainUpdated: 'accountHomeDomainUpdated',
15
+ accountThresholdsUpdated: 'accountThresholdsUpdated',
16
+ accountFlagsUpdated: 'accountFlagsUpdated',
17
+ accountInflationDestinationUpdated: 'accountInflationDestinationUpdated',
18
+
19
+ accountSignerUpdated: 'accountSignerUpdated',
20
+ accountSignerRemoved: 'accountSignerRemoved',
21
+ accountSignerCreated: 'accountSignerCreated',
22
+
23
+ trustlineCreated: 'trustlineCreated',
24
+ trustlineUpdated: 'trustlineUpdated',
25
+ trustlineRemoved: 'trustlineRemoved',
26
+ trustlineAuthorizationUpdated: 'trustlineAuthorizationUpdated',
27
+
28
+ assetMinted: 'assetMinted',
29
+ assetBurned: 'assetBurned',
30
+
31
+ liquidityPoolCreated: 'liquidityPoolCreated',
32
+ liquidityPoolUpdated: 'liquidityPoolUpdated',
33
+ liquidityPoolRemoved: 'liquidityPoolRemoved',
34
+
35
+ offerCreated: 'offerCreated',
36
+ offerUpdated: 'offerUpdated',
37
+ offerRemoved: 'offerRemoved',
38
+
39
+ trade: 'trade',
40
+
41
+ inflation: 'inflation',
42
+
43
+ sequenceBumped: 'sequenceBumped',
44
+
45
+ dataEntryCreated: 'dataEntryCreated',
46
+ dataEntryUpdated: 'dataEntryUpdated',
47
+ dataEntryRemoved: 'dataEntryRemoved',
48
+
49
+ claimableBalanceCreated: 'claimableBalanceCreated',
50
+ claimableBalanceRemoved: 'claimableBalanceRemoved',
51
+
52
+ liquidityPoolDeposited: 'liquidityPoolDeposited',
53
+ liquidityPoolWithdrew: 'liquidityPoolWithdrew',
54
+
55
+ accountSponsorshipCreated: 'accountSponsorshipCreated',
56
+ accountSponsorshipUpdated: 'accountSponsorshipUpdated',
57
+ accountSponsorshipRemoved: 'accountSponsorshipRemoved',
58
+
59
+ trustlineSponsorshipCreated: 'trustlineSponsorshipCreated',
60
+ trustlineSponsorshipUpdated: 'trustlineSponsorshipUpdated',
61
+ trustlineSponsorshipRemoved: 'trustlineSponsorshipRemoved',
62
+
63
+ offerSponsorshipCreated: 'offerSponsorshipCreated',
64
+ offerSponsorshipUpdated: 'offerSponsorshipUpdated',
65
+ offerSponsorshipRemoved: 'offerSponsorshipRemoved',
66
+
67
+ dataSponsorshipCreated: 'dataSponsorshipCreated',
68
+ dataSponsorshipUpdated: 'dataSponsorshipUpdated',
69
+ dataSponsorshipRemoved: 'dataSponsorshipRemoved',
70
+
71
+ claimableBalanceSponsorshipCreated: 'claimableBalanceSponsorshipCreated',
72
+ claimableBalanceSponsorshipUpdated: 'claimableBalanceSponsorshipUpdated',
73
+ claimableBalanceSponsorshipRemoved: 'claimableBalanceSponsorshipRemoved',
74
+
75
+ liquidityPoolSponsorshipCreated: 'liquidityPoolSponsorshipCreated',
76
+ liquidityPoolSponsorshipUpdated: 'liquidityPoolSponsorshipUpdated',
77
+ liquidityPoolSponsorshipRemoved: 'liquidityPoolSponsorshipRemoved',
78
+
79
+ signerSponsorshipCreated: 'signerSponsorshipCreated',
80
+ signerSponsorshipUpdated: 'signerSponsorshipUpdated',
81
+ signerSponsorshipRemoved: 'signerSponsorshipRemoved',
82
+
83
+ contractCodeUploaded: 'contractCodeUploaded',
84
+
85
+ contractCreated: 'contractCreated',
86
+ contractUpdated: 'contractUpdated',
87
+
88
+ contractInvoked: 'contractInvoked',
89
+
90
+ contractDataCreated: 'contractDataCreated',
91
+ contractDataUpdated: 'contractDataUpdated',
92
+ contractDataRemoved: 'contractDataRemoved',
93
+
94
+ contractEvent: 'contractEvent'
95
+ }
96
+
97
+ module.exports = effectTypes
package/src/effect.js ADDED
@@ -0,0 +1,24 @@
1
+ class Effect {
2
+ constructor(type, source) {
3
+ }
4
+
5
+ /**
6
+ * @type {String}
7
+ * @readonly
8
+ */
9
+ type
10
+
11
+ /**
12
+ * @type {String}
13
+ * @readonly
14
+ */
15
+ source
16
+
17
+ toString() {
18
+ return this.toJSON()
19
+ }
20
+
21
+ toJSON() {
22
+
23
+ }
24
+ }
package/src/errors.js ADDED
@@ -0,0 +1,13 @@
1
+ class TxMetaEffectParserError extends Error {
2
+ constructor(message) {
3
+ super('Transaction metadata processing error. ' + message)
4
+ }
5
+ }
6
+
7
+ class UnexpectedTxMetaChangeError extends TxMetaEffectParserError {
8
+ constructor({type, action}) {
9
+ super(`Unexpected meta changes: "${type}" "${action}"`)
10
+ }
11
+ }
12
+
13
+ module.exports = {UnexpectedTxMetaChangeError, TxMetaEffectParserError}