forked from Colored-Coins/cc-get-assets-outputs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
156 lines (137 loc) · 5.74 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
var assetIdencoder = require('cc-assetid-encoder')
var debug = require('debug')('cc-get-assets-outputs')
var _ = require('lodash')
module.exports = function (raw_transaction) {
var transaction_data = JSON.parse(JSON.stringify(raw_transaction))
var ccdata = transaction_data.ccdata[0]
var assets = []
if (ccdata.type === 'issuance') {
transaction_data.vin[0].assets = transaction_data.vin[0].assets || []
transaction_data.vin[0].assets.unshift({
assetId: assetIdencoder(transaction_data),
amount: ccdata.amount,
issueTxid: transaction_data.txid,
divisibility: ccdata.divisibility,
lockStatus: ccdata.lockStatus,
aggregationPolicy: ccdata.aggregationPolicy
})
}
var payments = ccdata.payments
var overflow = !transfer(assets, payments, transaction_data)
if (overflow) {
// transfer failed. transfer all assets in inputs to last output, aggregate those possible
assets.length = 0
transferToLastOutput(assets, transaction_data.vin, transaction_data.vout.length - 1)
}
raw_transaction.overflow = overflow
return assets
}
// returns true if succeeds to apply payments to the given assets array, false if runs into an invalid payment
function transfer (assets, payments, transaction_data) {
debug('transfer')
var _payments = _.cloneDeep(payments)
var _inputs = _.cloneDeep(transaction_data.vin)
var currentInputIndex = 0
var currentAssetIndex = 0
var payment
var currentAsset
var currentAmount
var lastPaymentIndex // aggregate only if paying the same payment
for (var i = 0; i < _payments.length; i++) {
payment = _payments[i]
debug('payment = ', payment)
if (!isPaymentSimple(payment)) {
return false
}
if (payment.input >= transaction_data.vin.length) {
return false
}
if (payment.output >= transaction_data.vout.length) {
return false
}
if (!payment.amount) {
debug('payment.amount === 0 before paying it, continue')
continue
}
if (currentInputIndex < payment.input) {
currentInputIndex = payment.input
currentAssetIndex = 0
}
if (currentInputIndex >= _inputs.length || !_inputs[currentInputIndex].assets || currentAssetIndex >= _inputs[currentInputIndex].assets.length || !_inputs[currentInputIndex].assets[currentAssetIndex]) {
debug('no asset in input #' + currentInputIndex + ' asset #' + currentAssetIndex + ', overflow')
return false
}
currentAsset = _inputs[currentInputIndex].assets[currentAssetIndex]
currentAmount = Math.min(payment.amount, currentAsset.amount)
debug('paying ' + currentAmount + ' ' + currentAsset.assetId + ' from input #' + currentInputIndex + ' asset #' + currentAssetIndex + ' to output #' + payment.output)
if (!payment.burn) {
assets[payment.output] = assets[payment.output] || []
debug('assets[' + payment.output + '] = ', assets[payment.output])
if (lastPaymentIndex === i) {
if (!assets[payment.output].length || assets[payment.output][assets[payment.output].length - 1].assetId !== currentAsset.assetId || currentAsset.aggregationPolicy !== 'aggregatable') {
debug('tried to pay same payment with a separate asset, overflow')
return false
}
debug('aggregating ' + currentAmount + ' of asset ' + currentAsset.assetId + ' from input #' + currentInputIndex + ' asset #' + currentAssetIndex + ' to output #' + payment.output)
assets[payment.output][assets[payment.output].length - 1].amount += currentAmount
} else {
assets[payment.output].push({
assetId: currentAsset.assetId,
amount: currentAmount,
issueTxid: currentAsset.issueTxid,
divisibility: currentAsset.divisibility,
lockStatus: currentAsset.lockStatus,
aggregationPolicy: currentAsset.aggregationPolicy
})
}
}
currentAsset.amount -= currentAmount
payment.amount -= currentAmount
if (currentAsset.amount === 0) {
currentAssetIndex++
while (currentInputIndex < _inputs.length && currentAssetIndex > _inputs[currentInputIndex].assets.length - 1) {
currentAssetIndex = 0
currentInputIndex++
}
}
debug('input #' + currentInputIndex + ', asset # ' + currentAssetIndex)
lastPaymentIndex = i
if (payment.amount) {
debug('payment not completed, stay on current payment')
i--
}
}
// finished paying explicit payments, transfer all assets with remaining amount from inputs to last output. aggregate if possible.
transferToLastOutput(assets, _inputs, transaction_data.vout.length - 1)
return true
}
// transfer all positive amount assets from inputs to last output. aggregate if possible.
function transferToLastOutput (assets, inputs, lastOutputIndex) {
var assetsToTransfer = []
inputs.forEach(function (input) {
assetsToTransfer = _.concat(assetsToTransfer, input.assets)
})
var assetsIndexes = {}
var lastOutputAssets = []
assetsToTransfer.forEach(function (asset, index) {
if (asset.aggregationPolicy === 'aggregatable' && (typeof assetsIndexes[asset.assetId] !== 'undefined')) {
lastOutputAssets[assetsIndexes[asset.assetId]].amount += asset.amount
} else if (asset.amount > 0) {
if (typeof assetsIndexes[asset.assetId] === 'undefined') {
assetsIndexes[asset.assetId] = lastOutputAssets.length
}
lastOutputAssets.push({
assetId: asset.assetId,
amount: asset.amount,
issueTxid: asset.issueTxid,
divisibility: asset.divisibility,
lockStatus: asset.lockStatus,
aggregationPolicy: asset.aggregationPolicy
})
}
})
assets[lastOutputIndex] = _.concat((assets[lastOutputIndex] || []), lastOutputAssets)
}
function isPaymentSimple (payment) {
return (!payment.range && !payment.percent)
}