@wharfkit/resources 1.0.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/src/index.ts ADDED
@@ -0,0 +1,87 @@
1
+ import {API, APIClient, APIClientOptions, FetchProvider, UInt128} from '@wharfkit/antelope'
2
+ import BN from 'bn.js'
3
+
4
+ import {PowerUpAPI} from './powerup'
5
+ import {RAMAPI} from './ram'
6
+ import {REXAPI} from './rex'
7
+
8
+ export * from './powerup'
9
+ export * from './ram'
10
+ export * from './rex'
11
+
12
+ interface ResourcesOptions extends APIClientOptions {
13
+ api?: APIClient
14
+ sampleAccount?: string
15
+ symbol?: string
16
+ url?: string
17
+ }
18
+
19
+ export interface SampleUsage {
20
+ account: API.v1.AccountObject
21
+ cpu: UInt128
22
+ net: UInt128
23
+ }
24
+
25
+ export const BNPrecision = new BN(1000 * 1000)
26
+
27
+ export class Resources {
28
+ static __className = 'Resources'
29
+
30
+ readonly api: APIClient
31
+
32
+ // the account to use when sampling usage
33
+ sampleAccount = 'b1'
34
+
35
+ // token precision/symbol
36
+ symbol = '4,EOS'
37
+
38
+ constructor(options: ResourcesOptions) {
39
+ // Allow overriding of the sample account name
40
+ if (options.sampleAccount) {
41
+ this.sampleAccount = options.sampleAccount
42
+ }
43
+ // Allow overriding of the system token symbol
44
+ if (options.symbol) {
45
+ this.symbol = options.symbol
46
+ }
47
+ // Allow variations on how to specify the API configuration
48
+ if (options.api) {
49
+ this.api = options.api
50
+ } else if (options.url) {
51
+ this.api = new APIClient({provider: new FetchProvider(options.url, options)})
52
+ } else {
53
+ throw new Error('Missing url or api client')
54
+ }
55
+ }
56
+
57
+ v1 = {
58
+ powerup: new PowerUpAPI(this),
59
+ ram: new RAMAPI(this),
60
+ rex: new REXAPI(this),
61
+ }
62
+
63
+ async getSampledUsage(): Promise<SampleUsage> {
64
+ const account = await this.api.v1.chain.get_account(this.sampleAccount)
65
+ const us = UInt128.from(account.cpu_limit.max.value.mul(BNPrecision))
66
+ const byte = UInt128.from(account.net_limit.max.value.mul(BNPrecision))
67
+
68
+ const cpu_weight = UInt128.from(account.cpu_weight.value)
69
+ const net_weight = UInt128.from(account.net_weight.value)
70
+
71
+ return {
72
+ account,
73
+ cpu: divCeil(us.value, cpu_weight.value),
74
+ net: divCeil(byte.value, net_weight.value),
75
+ }
76
+ }
77
+ }
78
+
79
+ function divCeil(num: BN, den: BN): UInt128 {
80
+ let v: BN = num.div(den)
81
+ const zero = new BN(0)
82
+ const one = new BN(1)
83
+ if (num.mod(den).gt(zero)) {
84
+ v = v.sub(one)
85
+ }
86
+ return UInt128.from(v)
87
+ }
@@ -0,0 +1,144 @@
1
+ import {
2
+ Asset,
3
+ Float64,
4
+ Int64,
5
+ Struct,
6
+ TimePointSec,
7
+ UInt128,
8
+ UInt32,
9
+ UInt64,
10
+ UInt8,
11
+ } from '@wharfkit/antelope'
12
+
13
+ import BN from 'bn.js'
14
+ import {PowerUpStateOptions} from './options'
15
+
16
+ export abstract class PowerUpStateResource extends Struct {
17
+ @Struct.field('uint8') version!: UInt8
18
+ @Struct.field('int64') weight!: Int64
19
+ @Struct.field('int64') weight_ratio!: Int64
20
+ @Struct.field('int64') assumed_stake_weight!: Int64
21
+ @Struct.field('int64') initial_weight_ratio!: Int64
22
+ @Struct.field('int64') target_weight_ratio!: Int64
23
+ @Struct.field('time_point_sec') initial_timestamp!: TimePointSec
24
+ @Struct.field('time_point_sec') target_timestamp!: TimePointSec
25
+ @Struct.field('float64') exponent!: Float64
26
+ @Struct.field('uint32') decay_secs!: UInt32
27
+ @Struct.field('asset') min_price!: Asset
28
+ @Struct.field('asset') max_price!: Asset
29
+ @Struct.field('int64') utilization!: Int64
30
+ @Struct.field('int64') adjusted_utilization!: Int64
31
+ @Struct.field('time_point_sec') utilization_timestamp!: TimePointSec
32
+
33
+ readonly default_block_cpu_limit: UInt64 = UInt64.from(200000)
34
+ readonly default_block_net_limit: UInt64 = UInt64.from(1048576000)
35
+
36
+ abstract per_day(options?: PowerUpStateOptions): number
37
+
38
+ // Get the current number of allocated units (shift from REX -> PowerUp)
39
+ public get allocated() {
40
+ return 1 - Number(this.weight_ratio) / Number(this.target_weight_ratio) / 100
41
+ }
42
+
43
+ // Get the current percentage of reserved units
44
+ public get reserved() {
45
+ return Number(this.utilization) / Number(this.weight)
46
+ }
47
+
48
+ // Get the symbol definition for the token
49
+ public get symbol() {
50
+ return this.min_price.symbol
51
+ }
52
+
53
+ // Common casting for typed values to numbers
54
+ cast() {
55
+ return {
56
+ adjusted_utilization: Number(this.adjusted_utilization),
57
+ decay_secs: Number(this.decay_secs.value),
58
+ exponent: Number(this.exponent),
59
+ utilization: Number(this.utilization),
60
+ utilization_timestamp: Number(this.utilization_timestamp.value),
61
+ weight: Number(this.weight),
62
+ weight_ratio: Number(this.weight_ratio),
63
+ }
64
+ }
65
+
66
+ // Mimic: https://github.com/EOSIO/eosio.contracts/blob/d7bc0a5cc8c0c2edd4dc61b0126517d0cb46fd94/contracts/eosio.system/src/powerup.cpp#L358
67
+ utilization_increase(sample: UInt128, frac) {
68
+ const {weight} = this
69
+ const frac128 = UInt128.from(frac)
70
+ const utilization_increase =
71
+ new BN(weight.value.mul(new BN(frac128.value))) / Math.pow(10, 15)
72
+ return Math.ceil(utilization_increase)
73
+ }
74
+
75
+ // Mimic: https://github.com/EOSIO/eosio.contracts/blob/d7bc0a5cc8c0c2edd4dc61b0126517d0cb46fd94/contracts/eosio.system/src/powerup.cpp#L284-L298
76
+ price_function(utilization: number): number {
77
+ const {exponent, weight} = this.cast()
78
+ const max_price: number = this.max_price.value
79
+ const min_price: number = this.min_price.value
80
+ let price = min_price
81
+ const new_exponent = exponent - 1.0
82
+ if (new_exponent <= 0.0) {
83
+ return max_price
84
+ } else {
85
+ price += (max_price - min_price) * Math.pow(utilization / weight, new_exponent)
86
+ }
87
+ return price
88
+ }
89
+
90
+ // Mimic: https://github.com/EOSIO/eosio.contracts/blob/d7bc0a5cc8c0c2edd4dc61b0126517d0cb46fd94/contracts/eosio.system/src/powerup.cpp#L274-L280
91
+ price_integral_delta(start_utilization: number, end_utilization: number): number {
92
+ const {exponent, weight} = this.cast()
93
+ const max_price: number = this.max_price.value
94
+ const min_price: number = this.min_price.value
95
+ const coefficient = (max_price - min_price) / exponent
96
+ const start_u = start_utilization / weight
97
+ const end_u = end_utilization / weight
98
+ const delta =
99
+ min_price * end_u -
100
+ min_price * start_u +
101
+ coefficient * Math.pow(end_u, exponent) -
102
+ coefficient * Math.pow(start_u, exponent)
103
+ return delta
104
+ }
105
+
106
+ // Mimic: https://github.com/EOSIO/eosio.contracts/blob/d7bc0a5cc8c0c2edd4dc61b0126517d0cb46fd94/contracts/eosio.system/src/powerup.cpp#L262-L315
107
+ fee(utilization_increase, adjusted_utilization) {
108
+ const {utilization, weight} = this.cast()
109
+
110
+ let start_utilization: number = utilization
111
+ const end_utilization: number = start_utilization + utilization_increase
112
+
113
+ let fee = 0
114
+ if (start_utilization < adjusted_utilization) {
115
+ fee +=
116
+ (this.price_function(adjusted_utilization) *
117
+ Math.min(utilization_increase, adjusted_utilization - start_utilization)) /
118
+ weight
119
+ start_utilization = adjusted_utilization
120
+ }
121
+ if (start_utilization < end_utilization) {
122
+ fee += this.price_integral_delta(start_utilization, end_utilization)
123
+ }
124
+ return fee
125
+ }
126
+
127
+ // Mimic: https://github.com/EOSIO/eosio.contracts/blob/d7bc0a5cc8c0c2edd4dc61b0126517d0cb46fd94/contracts/eosio.system/src/powerup.cpp#L105-L117
128
+ determine_adjusted_utilization(options?: PowerUpStateOptions) {
129
+ // Casting EOSIO types to usable formats for JS calculations
130
+ const {decay_secs, utilization, utilization_timestamp} = this.cast()
131
+ let {adjusted_utilization} = this.cast()
132
+ // If utilization is less than adjusted, calculate real time value
133
+ if (utilization < adjusted_utilization) {
134
+ // Create now & adjust JS timestamp to match EOSIO timestamp values
135
+ const ts = options && options.timestamp ? options.timestamp : new Date()
136
+ const now = TimePointSec.from(ts).toMilliseconds() / 1000
137
+ const diff: number = adjusted_utilization - utilization
138
+ let delta: number = diff * Math.exp(-(now - utilization_timestamp) / decay_secs)
139
+ delta = Math.min(Math.max(delta, 0), diff) // Clamp the delta
140
+ adjusted_utilization = utilization + delta
141
+ }
142
+ return adjusted_utilization
143
+ }
144
+ }
@@ -0,0 +1,76 @@
1
+ import {Struct, UInt128} from '@wharfkit/antelope'
2
+
3
+ import {BNPrecision, SampleUsage} from '..'
4
+ import {PowerUpStateResource} from './abstract'
5
+ import {PowerUpStateOptions} from './options'
6
+
7
+ @Struct.type('powerupstateresourcecpu')
8
+ export class PowerUpStateResourceCPU extends PowerUpStateResource {
9
+ // Return smallest units per day, μs (microseconds)
10
+ per_day = (options?: PowerUpStateOptions) => this.us_per_day(options)
11
+
12
+ // Return ms (milliseconds) per day
13
+ ms_per_day(options?: PowerUpStateOptions) {
14
+ return this.us_per_day(options) / 1000
15
+ }
16
+
17
+ // Return μs (microseconds) per day
18
+ us_per_day(options?: PowerUpStateOptions) {
19
+ const limit =
20
+ options && options.virtual_block_cpu_limit
21
+ ? options.virtual_block_cpu_limit
22
+ : this.default_block_cpu_limit
23
+ return Number(limit) * 2 * 60 * 60 * 24
24
+ }
25
+
26
+ // Convert weight to μs (microseconds)
27
+ weight_to_us(sample: UInt128, weight: number): number {
28
+ return Math.ceil((weight * Number(sample)) / BNPrecision)
29
+ }
30
+
31
+ // Convert μs (microseconds) to weight
32
+ us_to_weight(sample: UInt128, us: number): number {
33
+ return Math.floor((us / Number(sample)) * BNPrecision)
34
+ }
35
+
36
+ // Default frac generation by smallest unit type
37
+ frac = (usage: SampleUsage, us: number) => this.frac_by_us(usage, us)
38
+
39
+ // Frac generation by ms (milliseconds)
40
+ frac_by_ms = (usage: SampleUsage, ms: number) => this.frac_by_us(usage, ms * 1000)
41
+
42
+ // Frac generation by μs (microseconds)
43
+ frac_by_us(usage: SampleUsage, us: number) {
44
+ const {weight} = this.cast()
45
+ const frac = this.us_to_weight(usage.cpu, us) / weight
46
+ return Math.floor(frac * Math.pow(10, 15))
47
+ }
48
+
49
+ // Price generation by smallest units, μs (microseconds)
50
+ price_per = (usage: SampleUsage, us = 1000, options?: PowerUpStateOptions): number =>
51
+ this.price_per_us(usage, us, options)
52
+
53
+ // Price generation by ms (milliseconds)
54
+ price_per_ms = (usage: SampleUsage, ms = 1, options?: PowerUpStateOptions): number =>
55
+ this.price_per_us(usage, ms * 1000, options)
56
+
57
+ // Price generation by μs (microseconds)
58
+ price_per_us(usage: SampleUsage, us = 1000, options?: PowerUpStateOptions): number {
59
+ // Determine the utilization increase by this action
60
+ const frac = UInt128.from(this.frac(usage, us))
61
+ const utilization_increase = this.utilization_increase(usage.cpu, frac)
62
+
63
+ // Determine the adjusted utilization if needed
64
+ const adjusted_utilization = this.determine_adjusted_utilization(options)
65
+
66
+ // Derive the fee from the increase and utilization
67
+ const fee = this.fee(utilization_increase, adjusted_utilization)
68
+
69
+ // Force the fee up to the next highest value of precision
70
+ const precision = Math.pow(10, 4)
71
+ const value = Math.ceil(fee * precision) / precision
72
+
73
+ // Return the modified fee
74
+ return value
75
+ }
76
+ }
@@ -0,0 +1,77 @@
1
+ import {Struct, UInt128} from '@wharfkit/antelope'
2
+
3
+ import {BNPrecision, SampleUsage} from '..'
4
+ import {PowerUpStateResource} from './abstract'
5
+ import {PowerUpStateOptions} from './options'
6
+
7
+ @Struct.type('powerupstateresourcenet')
8
+ export class PowerUpStateResourceNET extends PowerUpStateResource {
9
+ // Return smallest units per day, bytes
10
+ per_day = (options?: PowerUpStateOptions) => this.bytes_per_day(options)
11
+
12
+ // Return kb per day
13
+ kb_per_day(options?: PowerUpStateOptions) {
14
+ return this.bytes_per_day(options) / 1000
15
+ }
16
+
17
+ // Return bytes per day
18
+ bytes_per_day(options?: PowerUpStateOptions) {
19
+ const limit =
20
+ options && options.virtual_block_net_limit
21
+ ? options.virtual_block_net_limit
22
+ : this.default_block_net_limit
23
+ return Number(limit) * 2 * 60 * 60 * 24
24
+ }
25
+
26
+ // Convert weight to bytes
27
+ weight_to_bytes(sample: UInt128, weight: number): number {
28
+ return Math.ceil((weight * Number(sample)) / BNPrecision)
29
+ }
30
+
31
+ // Convert bytes to weight
32
+ bytes_to_weight(sample: UInt128, bytes: number): number {
33
+ return Math.floor((bytes / Number(sample)) * BNPrecision)
34
+ }
35
+
36
+ // Default frac generation by smallest unit type
37
+ frac = (usage: SampleUsage, bytes: number) => this.frac_by_bytes(usage, bytes)
38
+
39
+ // Frac generation by kb
40
+ frac_by_kb = (usage: SampleUsage, kilobytes: number) =>
41
+ this.frac_by_bytes(usage, kilobytes * 1000)
42
+
43
+ // Frac generation by bytes
44
+ frac_by_bytes(usage: SampleUsage, bytes: number) {
45
+ const {weight} = this.cast()
46
+ const frac = this.bytes_to_weight(usage.net, bytes) / weight
47
+ return Math.floor(frac * Math.pow(10, 15))
48
+ }
49
+
50
+ // Price generation by smallest units, bytes
51
+ price_per = (usage: SampleUsage, bytes = 1000, options?: PowerUpStateOptions) =>
52
+ this.price_per_byte(usage, bytes, options)
53
+
54
+ // Price generation by kb
55
+ price_per_kb = (usage: SampleUsage, kilobytes = 1, options?: PowerUpStateOptions): number =>
56
+ this.price_per_byte(usage, kilobytes * 1000, options)
57
+
58
+ // Price generation by bytes
59
+ price_per_byte(usage: SampleUsage, bytes = 1000, options?: PowerUpStateOptions): number {
60
+ // Determine the utilization increase by this action
61
+ const frac = UInt128.from(this.frac(usage, bytes))
62
+ const utilization_increase = this.utilization_increase(usage.net, frac)
63
+
64
+ // Determine the adjusted utilization if needed
65
+ const adjusted_utilization = this.determine_adjusted_utilization(options)
66
+
67
+ // Derive the fee from the increase and utilization
68
+ const fee = this.fee(utilization_increase, adjusted_utilization)
69
+
70
+ // Force the fee up to the next highest value of precision
71
+ const precision = Math.pow(10, 4)
72
+ const value = Math.ceil(fee * precision) / precision
73
+
74
+ // Return the modified fee
75
+ return value
76
+ }
77
+ }
@@ -0,0 +1,9 @@
1
+ import {TimePointType, UInt64} from '@wharfkit/antelope'
2
+
3
+ export interface PowerUpStateOptions {
4
+ // timestamp to base adjusted_utilization off
5
+ timestamp?: TimePointType
6
+ // blockchain resource limits for calculating usage
7
+ virtual_block_cpu_limit?: UInt64
8
+ virtual_block_net_limit?: UInt64
9
+ }
package/src/powerup.ts ADDED
@@ -0,0 +1,28 @@
1
+ import {Asset, Struct, UInt32, UInt8} from '@wharfkit/antelope'
2
+
3
+ import {Resources} from '.'
4
+ import {PowerUpStateResourceCPU} from './powerup/cpu'
5
+ import {PowerUpStateResourceNET} from './powerup/net'
6
+
7
+ @Struct.type('powerupstate')
8
+ export class PowerUpState extends Struct {
9
+ @Struct.field('uint8') version!: UInt8
10
+ @Struct.field(PowerUpStateResourceNET) net!: PowerUpStateResourceNET
11
+ @Struct.field(PowerUpStateResourceCPU) cpu!: PowerUpStateResourceCPU
12
+ @Struct.field('uint32') powerup_days!: UInt32
13
+ @Struct.field('asset') min_powerup_fee!: Asset
14
+ }
15
+
16
+ export class PowerUpAPI {
17
+ constructor(private parent: Resources) {}
18
+
19
+ async get_state(): Promise<PowerUpState> {
20
+ const response = await this.parent.api.v1.chain.get_table_rows({
21
+ code: 'eosio',
22
+ scope: '',
23
+ table: 'powup.state',
24
+ type: PowerUpState,
25
+ })
26
+ return response.rows[0]
27
+ }
28
+ }
package/src/ram.ts ADDED
@@ -0,0 +1,52 @@
1
+ import {Resources} from './'
2
+
3
+ import {Asset, Float64, Struct} from '@wharfkit/antelope'
4
+
5
+ @Struct.type('connector')
6
+ export class Connector extends Struct {
7
+ @Struct.field('asset') balance!: Asset
8
+ @Struct.field('float64') weight!: Float64
9
+ }
10
+
11
+ @Struct.type('exchange_state')
12
+ export class ExchangeState extends Struct {
13
+ @Struct.field('asset') supply!: Asset
14
+ @Struct.field(Connector) base!: Connector
15
+ @Struct.field(Connector) quote!: Connector
16
+ }
17
+
18
+ @Struct.type('ramstate')
19
+ export class RAMState extends ExchangeState {
20
+ public price_per(bytes: number): number {
21
+ const base = this.base.balance.units.toNumber()
22
+ const quote = this.quote.balance.value
23
+ return this.get_input(base, quote, bytes)
24
+ }
25
+
26
+ public price_per_kb(kilobytes: number): number {
27
+ return this.price_per(kilobytes * 1000)
28
+ }
29
+
30
+ // Derived from https://github.com/EOSIO/eosio.contracts/blob/f6578c45c83ec60826e6a1eeb9ee71de85abe976/contracts/eosio.system/src/exchange_state.cpp#L96
31
+ public get_input(base: number, quote: number, value: number): number {
32
+ const result = (quote * value) / (base - value)
33
+ if (result < 0) {
34
+ return 0
35
+ }
36
+ return result
37
+ }
38
+ }
39
+
40
+ export class RAMAPI {
41
+ constructor(private parent: Resources) {}
42
+
43
+ async get_state() {
44
+ const response = await this.parent.api.v1.chain.get_table_rows({
45
+ code: 'eosio',
46
+ scope: 'eosio',
47
+ table: 'rammarket',
48
+ type: RAMState,
49
+ })
50
+ return response.rows[0]
51
+ }
52
+ }
package/src/rex.ts ADDED
@@ -0,0 +1,75 @@
1
+ import {BNPrecision, Resources, SampleUsage} from './'
2
+
3
+ import {Asset, Struct, UInt64, UInt8} from '@wharfkit/antelope'
4
+
5
+ @Struct.type('rexstate')
6
+ export class REXState extends Struct {
7
+ @Struct.field('uint8') version!: UInt8
8
+ @Struct.field('asset') total_lent!: Asset
9
+ @Struct.field('asset') total_unlent!: Asset
10
+ @Struct.field('asset') total_rent!: Asset
11
+ @Struct.field('asset') total_lendable!: Asset
12
+ @Struct.field('asset') total_rex!: Asset
13
+ @Struct.field('asset') namebid_proceeds!: Asset
14
+ @Struct.field('uint64') loan_num!: UInt64
15
+
16
+ public get reserved() {
17
+ return Number(this.total_lent.units) / Number(this.total_lendable.units)
18
+ }
19
+
20
+ public get symbol() {
21
+ return this.total_lent.symbol
22
+ }
23
+
24
+ public get precision() {
25
+ return this.total_lent.symbol.precision
26
+ }
27
+
28
+ public get value() {
29
+ return (
30
+ (Number(this.total_lent.units) + Number(this.total_unlent.units)) /
31
+ Number(this.total_rex.units)
32
+ )
33
+ }
34
+
35
+ exchange(amount: Asset): Asset {
36
+ return Asset.from(
37
+ (amount.value * this.total_lendable.value) / this.total_rex.value,
38
+ this.symbol
39
+ )
40
+ }
41
+
42
+ price_per(sample: SampleUsage, unit = 1000): number {
43
+ // Sample token units
44
+ const tokens = Asset.fromUnits(10000, this.symbol)
45
+
46
+ // Spending 1 EOS (10000 units) on REX gives this many tokens
47
+ const bancor = Number(tokens.units) / (this.total_rent.value / this.total_unlent.value)
48
+
49
+ // The ratio of the number of tokens received vs the sampled values
50
+ const unitPrice = bancor * (Number(sample.cpu) / BNPrecision)
51
+
52
+ // The token units spent per unit
53
+ const perunit = Number(tokens.units) / unitPrice
54
+
55
+ // Multiply the per unit cost by the units requested
56
+ const cost = perunit * unit
57
+
58
+ // Converting to an Asset
59
+ return cost / Math.pow(10, this.precision)
60
+ }
61
+ }
62
+
63
+ export class REXAPI {
64
+ constructor(private parent: Resources) {}
65
+
66
+ async get_state() {
67
+ const response = await this.parent.api.v1.chain.get_table_rows({
68
+ code: 'eosio',
69
+ scope: 'eosio',
70
+ table: 'rexpool',
71
+ type: REXState,
72
+ })
73
+ return response.rows[0]
74
+ }
75
+ }