phantasma-sdk-ts 0.11.0 → 0.12.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 (29) hide show
  1. package/dist/cjs/rpc/interfaces/gas-config.js +64 -0
  2. package/dist/cjs/rpc/interfaces/index.js +1 -0
  3. package/dist/cjs/rpc/phantasma.js +10 -0
  4. package/dist/cjs/types/carbon/blockchain/gas-config.js +137 -0
  5. package/dist/cjs/types/carbon/blockchain/index.js +1 -0
  6. package/dist/cjs/types/carbon/blockchain/tx-helpers/index.js +1 -0
  7. package/dist/cjs/types/carbon/blockchain/tx-helpers/native-fee-estimator.js +268 -0
  8. package/dist/esm/rpc/interfaces/gas-config.js +61 -0
  9. package/dist/esm/rpc/interfaces/index.js +1 -0
  10. package/dist/esm/rpc/phantasma.js +10 -0
  11. package/dist/esm/types/carbon/blockchain/gas-config.js +133 -0
  12. package/dist/esm/types/carbon/blockchain/index.js +1 -0
  13. package/dist/esm/types/carbon/blockchain/tx-helpers/index.js +1 -0
  14. package/dist/esm/types/carbon/blockchain/tx-helpers/native-fee-estimator.js +263 -0
  15. package/dist/types/rpc/interfaces/gas-config.d.ts +61 -0
  16. package/dist/types/rpc/interfaces/gas-config.d.ts.map +1 -0
  17. package/dist/types/rpc/interfaces/index.d.ts +1 -0
  18. package/dist/types/rpc/interfaces/index.d.ts.map +1 -1
  19. package/dist/types/rpc/phantasma.d.ts +8 -0
  20. package/dist/types/rpc/phantasma.d.ts.map +1 -1
  21. package/dist/types/types/carbon/blockchain/gas-config.d.ts +63 -0
  22. package/dist/types/types/carbon/blockchain/gas-config.d.ts.map +1 -0
  23. package/dist/types/types/carbon/blockchain/index.d.ts +1 -0
  24. package/dist/types/types/carbon/blockchain/index.d.ts.map +1 -1
  25. package/dist/types/types/carbon/blockchain/tx-helpers/index.d.ts +1 -0
  26. package/dist/types/types/carbon/blockchain/tx-helpers/index.d.ts.map +1 -1
  27. package/dist/types/types/carbon/blockchain/tx-helpers/native-fee-estimator.d.ts +105 -0
  28. package/dist/types/types/carbon/blockchain/tx-helpers/native-fee-estimator.d.ts.map +1 -0
  29. package/package.json +1 -1
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.gasConfigFromRpc = gasConfigFromRpc;
4
+ const gas_config_js_1 = require("../../types/carbon/blockchain/gas-config.js");
5
+ /**
6
+ * Converts the getGasConfig JSON response to the wire-format GasConfig consumed by the Tier-1
7
+ * fee estimator. Throws on malformed numeric strings and on a v2 response missing tail fields:
8
+ * estimating fees from silently zeroed v2 prices would produce rejected transactions.
9
+ */
10
+ function gasConfigFromRpc(result) {
11
+ const c = result.gasConfig;
12
+ if (!c) {
13
+ throw new Error('getGasConfig response has no gasConfig section');
14
+ }
15
+ const config = new gas_config_js_1.GasConfig({
16
+ version: c.version,
17
+ maxNameLength: c.maxNameLength,
18
+ maxTokenSymbolLength: c.maxTokenSymbolLength,
19
+ feeShift: c.feeShift,
20
+ maxStructureSize: c.maxStructureSize,
21
+ feeMultiplier: parseU64(c.feeMultiplier, 'feeMultiplier'),
22
+ gasTokenId: parseU64(c.gasTokenId, 'gasTokenId'),
23
+ dataTokenId: parseU64(c.dataTokenId, 'dataTokenId'),
24
+ minimumGasOffer: parseU64(c.minimumGasOffer, 'minimumGasOffer'),
25
+ dataEscrowPerRow: parseU64(c.dataEscrowPerRow, 'dataEscrowPerRow'),
26
+ gasFeeTransfer: parseU64(c.gasFeeTransfer, 'gasFeeTransfer'),
27
+ gasFeeQuery: parseU64(c.gasFeeQuery, 'gasFeeQuery'),
28
+ gasFeeCreateTokenBase: parseU64(c.gasFeeCreateTokenBase, 'gasFeeCreateTokenBase'),
29
+ gasFeeCreateTokenSymbol: parseU64(c.gasFeeCreateTokenSymbol, 'gasFeeCreateTokenSymbol'),
30
+ gasFeeCreateTokenSeries: parseU64(c.gasFeeCreateTokenSeries, 'gasFeeCreateTokenSeries'),
31
+ gasFeePerByte: parseU64(c.gasFeePerByte, 'gasFeePerByte'),
32
+ gasFeeRegisterName: parseU64(c.gasFeeRegisterName, 'gasFeeRegisterName'),
33
+ gasBurnRatioMul: parseU64(c.gasBurnRatioMul, 'gasBurnRatioMul'),
34
+ gasBurnRatioShift: c.gasBurnRatioShift,
35
+ });
36
+ if (config.version >= 1) {
37
+ config.minimumGasBill = parseU64(c.minimumGasBill, 'minimumGasBill');
38
+ config.gasProducerRatioMul = parseU64(c.gasProducerRatioMul, 'gasProducerRatioMul');
39
+ config.gasProducerRatioShift = requireByte(c.gasProducerRatioShift, 'gasProducerRatioShift');
40
+ config.gasDappRatioMul = parseU64(c.gasDappRatioMul, 'gasDappRatioMul');
41
+ config.gasDappRatioShift = requireByte(c.gasDappRatioShift, 'gasDappRatioShift');
42
+ config.policyFeeCreateTokenBase = parseU64(c.policyFeeCreateTokenBase, 'policyFeeCreateTokenBase');
43
+ config.policyFeeCreateTokenSymbol = parseU64(c.policyFeeCreateTokenSymbol, 'policyFeeCreateTokenSymbol');
44
+ config.policyFeeCreateTokenSeries = parseU64(c.policyFeeCreateTokenSeries, 'policyFeeCreateTokenSeries');
45
+ config.policyFeeRegisterName = parseU64(c.policyFeeRegisterName, 'policyFeeRegisterName');
46
+ config.legacyDataEscrowPerRow = parseU64(c.legacyDataEscrowPerRow, 'legacyDataEscrowPerRow');
47
+ }
48
+ return config;
49
+ }
50
+ function parseU64(value, fieldName) {
51
+ if (value === undefined || value === '') {
52
+ throw new Error(`getGasConfig field ${fieldName} is missing or empty`);
53
+ }
54
+ if (!/^\d+$/.test(value)) {
55
+ throw new Error(`getGasConfig field ${fieldName} is not a decimal integer: ${value}`);
56
+ }
57
+ return BigInt(value);
58
+ }
59
+ function requireByte(value, fieldName) {
60
+ if (value === undefined) {
61
+ throw new Error(`getGasConfig field ${fieldName} is missing`);
62
+ }
63
+ return value;
64
+ }
@@ -30,6 +30,7 @@ __exportStar(require("./contract.js"), exports);
30
30
  __exportStar(require("./dapp.js"), exports);
31
31
  __exportStar(require("./event.js"), exports);
32
32
  __exportStar(require("./event-extended.js"), exports);
33
+ __exportStar(require("./gas-config.js"), exports);
33
34
  __exportStar(require("./governance.js"), exports);
34
35
  __exportStar(require("./interop.js"), exports);
35
36
  __exportStar(require("./key-value.js"), exports);
@@ -416,6 +416,16 @@ class PhantasmaAPI {
416
416
  const params = [name, extended];
417
417
  return (await this.JSONRPC('getChain', params));
418
418
  }
419
+ /**
420
+ * Returns the current on-chain gas configuration plus the chain parameters fee estimation
421
+ * needs (block rate target, expiry window). Changes only via governance resolutions, so the
422
+ * result is safe to cache. Feed gasConfigFromRpc() into estimateNativeFee() for Tier-1 fee
423
+ * estimates.
424
+ */
425
+ async getGasConfig() {
426
+ const params = [];
427
+ return (await this.JSONRPC('getGasConfig', params));
428
+ }
419
429
  //Returns info about the nexus.
420
430
  // Warning: current Carbon RPC endpoint is stubbed and returns a default nexus object.
421
431
  async getNexus(extended = true) {
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GasConfig = void 0;
4
+ /**
5
+ * On-chain gas configuration (governance module). The gas-model-v2 extension fields serialize
6
+ * only for version >= 1, mirroring the node's data_blockchain.h wire format exactly: the
7
+ * version-0 byte image is frozen forever for historical replay, and a version>=1 image truncated
8
+ * to the v0 length fails to parse (the tail read throws on end of stream).
9
+ */
10
+ class GasConfig {
11
+ constructor(init) {
12
+ this.version = 0;
13
+ this.maxNameLength = 0;
14
+ this.maxTokenSymbolLength = 0;
15
+ this.feeShift = 0;
16
+ this.maxStructureSize = 0;
17
+ this.feeMultiplier = 0n;
18
+ this.gasTokenId = 0n;
19
+ this.dataTokenId = 0n;
20
+ this.minimumGasOffer = 0n;
21
+ this.dataEscrowPerRow = 0n;
22
+ this.gasFeeTransfer = 0n;
23
+ this.gasFeeQuery = 0n;
24
+ this.gasFeeCreateTokenBase = 0n;
25
+ this.gasFeeCreateTokenSymbol = 0n;
26
+ this.gasFeeCreateTokenSeries = 0n;
27
+ this.gasFeePerByte = 0n;
28
+ this.gasFeeRegisterName = 0n;
29
+ this.gasBurnRatioMul = 0n;
30
+ this.gasBurnRatioShift = 0;
31
+ this.minimumGasBill = 0n;
32
+ this.gasProducerRatioMul = 0n;
33
+ this.gasProducerRatioShift = 0;
34
+ this.gasDappRatioMul = 0n;
35
+ this.gasDappRatioShift = 0;
36
+ this.policyFeeCreateTokenBase = 0n;
37
+ this.policyFeeCreateTokenSymbol = 0n;
38
+ this.policyFeeCreateTokenSeries = 0n;
39
+ this.policyFeeRegisterName = 0n;
40
+ this.legacyDataEscrowPerRow = 0n;
41
+ Object.assign(this, init);
42
+ }
43
+ /**
44
+ * True when this config activates the gas-model-v2 billing rules (config version >= 1). The
45
+ * gas model is gated by the config version, not by a chain feature level.
46
+ */
47
+ get hasGasModelV2() {
48
+ return this.version >= 1;
49
+ }
50
+ write(w) {
51
+ w.write1(this.version);
52
+ w.write1(this.maxNameLength);
53
+ w.write1(this.maxTokenSymbolLength);
54
+ w.write1(this.feeShift);
55
+ w.write4u(this.maxStructureSize);
56
+ w.write8u(this.feeMultiplier);
57
+ w.write8u(this.gasTokenId);
58
+ w.write8u(this.dataTokenId);
59
+ w.write8u(this.minimumGasOffer);
60
+ w.write8u(this.dataEscrowPerRow);
61
+ w.write8u(this.gasFeeTransfer);
62
+ w.write8u(this.gasFeeQuery);
63
+ w.write8u(this.gasFeeCreateTokenBase);
64
+ w.write8u(this.gasFeeCreateTokenSymbol);
65
+ w.write8u(this.gasFeeCreateTokenSeries);
66
+ w.write8u(this.gasFeePerByte);
67
+ w.write8u(this.gasFeeRegisterName);
68
+ w.write8u(this.gasBurnRatioMul);
69
+ w.write1(this.gasBurnRatioShift);
70
+ if (this.version === 0) {
71
+ // Version-0 wire image must stay byte-identical to the pre-v2 layout.
72
+ return;
73
+ }
74
+ w.write8u(this.minimumGasBill);
75
+ w.write8u(this.gasProducerRatioMul);
76
+ w.write1(this.gasProducerRatioShift);
77
+ w.write8u(this.gasDappRatioMul);
78
+ w.write1(this.gasDappRatioShift);
79
+ w.write8u(this.policyFeeCreateTokenBase);
80
+ w.write8u(this.policyFeeCreateTokenSymbol);
81
+ w.write8u(this.policyFeeCreateTokenSeries);
82
+ w.write8u(this.policyFeeRegisterName);
83
+ w.write8u(this.legacyDataEscrowPerRow);
84
+ }
85
+ read(r) {
86
+ this.version = r.read1();
87
+ this.maxNameLength = r.read1();
88
+ this.maxTokenSymbolLength = r.read1();
89
+ this.feeShift = r.read1();
90
+ this.maxStructureSize = r.read4u();
91
+ this.feeMultiplier = r.read8u();
92
+ this.gasTokenId = r.read8u();
93
+ this.dataTokenId = r.read8u();
94
+ this.minimumGasOffer = r.read8u();
95
+ this.dataEscrowPerRow = r.read8u();
96
+ this.gasFeeTransfer = r.read8u();
97
+ this.gasFeeQuery = r.read8u();
98
+ this.gasFeeCreateTokenBase = r.read8u();
99
+ this.gasFeeCreateTokenSymbol = r.read8u();
100
+ this.gasFeeCreateTokenSeries = r.read8u();
101
+ this.gasFeePerByte = r.read8u();
102
+ this.gasFeeRegisterName = r.read8u();
103
+ this.gasBurnRatioMul = r.read8u();
104
+ this.gasBurnRatioShift = r.read1();
105
+ if (this.version === 0) {
106
+ // Version-0 rows carry no v2 tail; zero it so a reused instance never leaks stale values.
107
+ this.minimumGasBill = 0n;
108
+ this.gasProducerRatioMul = 0n;
109
+ this.gasProducerRatioShift = 0;
110
+ this.gasDappRatioMul = 0n;
111
+ this.gasDappRatioShift = 0;
112
+ this.policyFeeCreateTokenBase = 0n;
113
+ this.policyFeeCreateTokenSymbol = 0n;
114
+ this.policyFeeCreateTokenSeries = 0n;
115
+ this.policyFeeRegisterName = 0n;
116
+ this.legacyDataEscrowPerRow = 0n;
117
+ return;
118
+ }
119
+ // version >= 1: the tail is mandatory; a truncated image throws (end of stream).
120
+ this.minimumGasBill = r.read8u();
121
+ this.gasProducerRatioMul = r.read8u();
122
+ this.gasProducerRatioShift = r.read1();
123
+ this.gasDappRatioMul = r.read8u();
124
+ this.gasDappRatioShift = r.read1();
125
+ this.policyFeeCreateTokenBase = r.read8u();
126
+ this.policyFeeCreateTokenSymbol = r.read8u();
127
+ this.policyFeeCreateTokenSeries = r.read8u();
128
+ this.policyFeeRegisterName = r.read8u();
129
+ this.legacyDataEscrowPerRow = r.read8u();
130
+ }
131
+ static read(r) {
132
+ const v = new GasConfig();
133
+ v.read(r);
134
+ return v;
135
+ }
136
+ }
137
+ exports.GasConfig = GasConfig;
@@ -19,6 +19,7 @@ __exportStar(require("./modules/index.js"), exports);
19
19
  __exportStar(require("./tx-helpers/index.js"), exports);
20
20
  __exportStar(require("./vm/index.js"), exports);
21
21
  __exportStar(require("./carbon-token-flags.js"), exports);
22
+ __exportStar(require("./gas-config.js"), exports);
22
23
  __exportStar(require("./module-id.js"), exports);
23
24
  __exportStar(require("./signed-tx-msg.js"), exports);
24
25
  __exportStar(require("./tx-msg.js"), exports);
@@ -19,3 +19,4 @@ __exportStar(require("./create-token-tx-helper.js"), exports);
19
19
  __exportStar(require("./fee-options.js"), exports);
20
20
  __exportStar(require("./mint-phantasma-non-fungible-tx-helper.js"), exports);
21
21
  __exportStar(require("./mint-non-fungible-tx-helper.js"), exports);
22
+ __exportStar(require("./native-fee-estimator.js"), exports);
@@ -0,0 +1,268 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NATIVE_SIGNATURE_BYTES = exports.WITNESS_ARRAY_ENTRY_BYTES = exports.GAS_MODEL_V2_UNITS_PER_BLOCK_DATA_BYTE = exports.NativeFeeKind = void 0;
4
+ exports.estimateNativeFee = estimateNativeFee;
5
+ exports.envelopeBytesFor = envelopeBytesFor;
6
+ /** Native operation kinds supported by the Tier-1 fee estimator. */
7
+ var NativeFeeKind;
8
+ (function (NativeFeeKind) {
9
+ /** Fungible token transfer (native TxTypes.TransferFungible / _GasPayer). */
10
+ NativeFeeKind["TransferFungible"] = "TransferFungible";
11
+ /** NFT transfer of `count` instances (TxTypes.TransferNonFungible_*). */
12
+ NativeFeeKind["TransferNonFungible"] = "TransferNonFungible";
13
+ /** Fungible mint (TxTypes.MintFungible or TokenContract.MintFungible call). */
14
+ NativeFeeKind["MintFungible"] = "MintFungible";
15
+ /** NFT mint of `count` instances (TxTypes.MintNonFungible or contract call). */
16
+ NativeFeeKind["MintNonFungible"] = "MintNonFungible";
17
+ /** Fungible burn (TxTypes.BurnFungible / _GasPayer). */
18
+ NativeFeeKind["BurnFungible"] = "BurnFungible";
19
+ /** NFT burn of `count` instances (TxTypes.BurnNonFungible / _GasPayer). */
20
+ NativeFeeKind["BurnNonFungible"] = "BurnNonFungible";
21
+ /** TokenContract.CreateToken call. Requires `symbolLength` when a symbol is set. */
22
+ NativeFeeKind["CreateToken"] = "CreateToken";
23
+ /** TokenContract.CreateTokenSeries call. */
24
+ NativeFeeKind["CreateTokenSeries"] = "CreateTokenSeries";
25
+ /** GovernanceContract.RegisterName call. Requires `nameLength`. */
26
+ NativeFeeKind["RegisterName"] = "RegisterName";
27
+ /**
28
+ * Generic Phantasma VM script transaction (AllowGas/SpendGas pattern: stake, marketplace,
29
+ * custom contract calls). Script opcode costs are not closed-form in Tier-1; the estimate
30
+ * budgets `scriptUnitsAllowance` VM work units on top of the byte fee. For an exact script
31
+ * bill use the node-side Tier-2 estimator once available.
32
+ */
33
+ NativeFeeKind["Script"] = "Script";
34
+ })(NativeFeeKind || (exports.NativeFeeKind = NativeFeeKind = {}));
35
+ /**
36
+ * Gas model v2 price of block-carried bytes, in gas units per byte. A versioned consensus
37
+ * constant of the v2 gas model (node data_blockchain.h kGasModelV2UnitsPerBlockDataByte),
38
+ * deliberately not part of the on-chain config.
39
+ */
40
+ exports.GAS_MODEL_V2_UNITS_PER_BLOCK_DATA_BYTE = 25n;
41
+ /** Serialized size of one witness-array entry (32-byte address + 64-byte signature). */
42
+ exports.WITNESS_ARRAY_ENTRY_BYTES = 96;
43
+ /** Serialized size of one bare signature (native TxTypes carry no witness array). */
44
+ exports.NATIVE_SIGNATURE_BYTES = 64;
45
+ const U64_MAX = 0xffffffffffffffffn;
46
+ /**
47
+ * Tier-1 static fee calculator: closed-form gas/data offers for native operations, exact under
48
+ * both gas models (selected by GasConfig.version). Mirrors the validator billing formula
49
+ * (node blockchain.cpp settlement + token/governance contract gas sites); any change to those
50
+ * formulas ships as a new gas-model version, never silently.
51
+ *
52
+ * @param kind - Operation kind.
53
+ * @param config - Current chain gas config (see the getGasConfig RPC method).
54
+ * @param params - Optional inputs; omit for safe single-signer defaults.
55
+ */
56
+ function estimateNativeFee(kind, config, params = {}) {
57
+ const count = params.count ?? 1;
58
+ if (!Number.isSafeInteger(count) || count < 1) {
59
+ throw new RangeError('count must be a positive integer');
60
+ }
61
+ const countU = BigInt(count);
62
+ const v2 = config.hasGasModelV2;
63
+ // Work units consumed by the operation itself (the ConsumeGas amounts in the token /
64
+ // governance contracts) and, under v2, the direct kcal-base policy fee that replaces the v1
65
+ // unit-priced product prices.
66
+ let workUnits;
67
+ let policyFee = 0n;
68
+ switch (kind) {
69
+ case NativeFeeKind.TransferFungible:
70
+ case NativeFeeKind.MintFungible:
71
+ case NativeFeeKind.BurnFungible:
72
+ workUnits = config.gasFeeTransfer;
73
+ break;
74
+ case NativeFeeKind.TransferNonFungible:
75
+ case NativeFeeKind.MintNonFungible:
76
+ case NativeFeeKind.BurnNonFungible:
77
+ workUnits = clampU64(config.gasFeeTransfer * countU);
78
+ break;
79
+ case NativeFeeKind.CreateToken: {
80
+ // Symbol price halves per character after the first; shift is validated by the chain
81
+ // against maxTokenSymbolLength, mirror that bound here.
82
+ const shift = symbolShift(params.symbolLength ?? 0, config.maxTokenSymbolLength, 'symbolLength');
83
+ const hasSymbol = (params.symbolLength ?? 0) > 0;
84
+ if (v2) {
85
+ workUnits = 0n;
86
+ policyFee = clampU64(config.policyFeeCreateTokenBase +
87
+ (hasSymbol ? config.policyFeeCreateTokenSymbol >> shift : 0n));
88
+ }
89
+ else {
90
+ workUnits = clampU64(config.gasFeeCreateTokenBase + (hasSymbol ? config.gasFeeCreateTokenSymbol >> shift : 0n));
91
+ }
92
+ break;
93
+ }
94
+ case NativeFeeKind.CreateTokenSeries:
95
+ workUnits = v2 ? 0n : config.gasFeeCreateTokenSeries;
96
+ policyFee = v2 ? config.policyFeeCreateTokenSeries : 0n;
97
+ break;
98
+ case NativeFeeKind.RegisterName: {
99
+ const nameLength = params.nameLength ?? 0;
100
+ if (nameLength <= 0) {
101
+ throw new RangeError('nameLength is required for RegisterName');
102
+ }
103
+ const shift = symbolShift(nameLength, config.maxNameLength, 'nameLength');
104
+ workUnits = v2 ? 0n : config.gasFeeRegisterName >> shift;
105
+ policyFee = v2 ? config.policyFeeRegisterName >> shift : 0n;
106
+ break;
107
+ }
108
+ case NativeFeeKind.Script:
109
+ workUnits = params.scriptUnitsAllowance ?? 5000n;
110
+ break;
111
+ default:
112
+ throw new RangeError(`unknown fee kind: ${kind}`);
113
+ }
114
+ const rows = BigInt(params.freshRows ?? defaultFreshRows(kind, count, params.romRamBytes ?? 0));
115
+ if (rows < 0n) {
116
+ throw new RangeError('freshRows must not be negative');
117
+ }
118
+ const envelope = BigInt(params.envelopeBytes ?? defaultEnvelopeBytes(kind, count, params.romRamBytes ?? 0));
119
+ // Native TxTypes store no events in the block; script txs do (Notify), so budget some.
120
+ const eventBytes = kind === NativeFeeKind.Script ? 512n : 0n;
121
+ let expected;
122
+ let maxGas;
123
+ if (v2) {
124
+ // v2 formula: bill = mulShiftSat(workUnits + blockData*25, mult, shift) + policyFee,
125
+ // floored at minimumGasBill. blockData = envelope + events + net storage quanta (quanta are
126
+ // added to the byte count by the chain formula).
127
+ expected = billV2(workUnits, envelope, eventBytes, rows, policyFee, config);
128
+ // Offer headroom: +25% over the padded bill covers witness-size wiggle and event variance;
129
+ // deterministic and always refunded down to the actual bill.
130
+ const padded = billV2(workUnits, roundUp(envelope, 128n), eventBytes, rows, policyFee, config);
131
+ maxGas = clampU64(padded + padded / 4n);
132
+ if (maxGas < config.minimumGasBill)
133
+ maxGas = config.minimumGasBill;
134
+ if (maxGas < config.minimumGasOffer)
135
+ maxGas = config.minimumGasOffer;
136
+ }
137
+ else {
138
+ // v1 formula: bill = (workUnits * mult >> shift) + blockData * gasFeePerByte where
139
+ // blockData = payload + events + net storage quanta (no envelope term, no floor).
140
+ const work = mulShift(workUnits, config.feeMultiplier, config.feeShift);
141
+ const blockData = BigInt(params.payloadBytes ?? 0) + eventBytes + rows;
142
+ expected = clampU64(work + clampU64(blockData * config.gasFeePerByte));
143
+ // Offer shape mirrors the validator's own test-agent stdFee: a 2x minimum-offer pad plus a
144
+ // flat 1 KiB block-data allowance on top of the work term.
145
+ const byteAllowance = blockData > 1024n ? blockData : 1024n;
146
+ maxGas = clampU64(config.minimumGasOffer * 2n + work + clampU64(byteAllowance * config.gasFeePerByte));
147
+ }
148
+ const maxData = clampU64(rows * config.dataEscrowPerRow);
149
+ return { maxGas, maxData, expectedGasBill: expected };
150
+ }
151
+ /**
152
+ * Envelope size (signed tx bytes as carried in the block) from a serialized unsigned message
153
+ * length and the number of signers. Use with {@link NativeFeeParams.envelopeBytes} for exact v2
154
+ * estimates. Witness layout mirrors SignedTxMsg: native TxTypes append bare 64-byte signatures
155
+ * (one, or two for the _GasPayer variants); Call/Trade/Phantasma txs append a length-prefixed
156
+ * witness array (32-byte address + 64-byte signature per entry).
157
+ */
158
+ function envelopeBytesFor(kind, serializedMessageLength, witnessCount = 1) {
159
+ if (!Number.isSafeInteger(serializedMessageLength) || serializedMessageLength < 0) {
160
+ throw new RangeError('serializedMessageLength must be a non-negative integer');
161
+ }
162
+ if (!Number.isSafeInteger(witnessCount) || witnessCount < 0) {
163
+ throw new RangeError('witnessCount must be a non-negative integer');
164
+ }
165
+ switch (kind) {
166
+ case NativeFeeKind.TransferFungible:
167
+ case NativeFeeKind.TransferNonFungible:
168
+ case NativeFeeKind.MintFungible:
169
+ case NativeFeeKind.MintNonFungible:
170
+ case NativeFeeKind.BurnFungible:
171
+ case NativeFeeKind.BurnNonFungible:
172
+ return serializedMessageLength + exports.NATIVE_SIGNATURE_BYTES * witnessCount;
173
+ default:
174
+ // CreateToken/CreateTokenSeries/RegisterName ride TxTypes.Call; Script rides
175
+ // TxTypes.Phantasma - both carry the witness array form.
176
+ return serializedMessageLength + 4 + exports.WITNESS_ARRAY_ENTRY_BYTES * witnessCount;
177
+ }
178
+ }
179
+ function billV2(workUnits, envelope, eventBytes, rows, policyFee, config) {
180
+ const blockData = envelope + eventBytes + rows;
181
+ const byteUnits = clampU64(blockData * exports.GAS_MODEL_V2_UNITS_PER_BLOCK_DATA_BYTE);
182
+ let bill = mulShift(clampU64(workUnits + byteUnits), config.feeMultiplier, config.feeShift);
183
+ bill = clampU64(bill + policyFee);
184
+ return bill < config.minimumGasBill ? config.minimumGasBill : bill;
185
+ }
186
+ // Chain fee scaling: (value * feeMultiplier) >> feeShift, saturating to u64. Matches the
187
+ // validator's v2 MulShiftSaturateU64; for sane v1 configs (live values never overflow 64 bits)
188
+ // it is also bit-identical to the v1 math.
189
+ function mulShift(value, multiplier, shift) {
190
+ if (shift >= 64)
191
+ return 0n; // the chain clamps oversized shifts to a zero delta
192
+ return clampU64((value * multiplier) >> BigInt(shift));
193
+ }
194
+ function symbolShift(length, maxLength, paramName) {
195
+ if (!Number.isSafeInteger(length) || length < 0) {
196
+ throw new RangeError(`${paramName} must be a non-negative integer`);
197
+ }
198
+ if (length === 0)
199
+ return 0n;
200
+ const shift = length - 1;
201
+ // The chain asserts shift < maxNameLength / maxTokenSymbolLength; a longer input could never
202
+ // be admitted, so reject it here instead of quoting a fee for an impossible tx.
203
+ if (maxLength !== 0 && shift >= maxLength) {
204
+ throw new RangeError(`${paramName} ${length} exceeds the chain maximum ${maxLength}`);
205
+ }
206
+ return BigInt(shift);
207
+ }
208
+ // Worst-case paid rows the operation can create (drives maxData). Refund-only operations
209
+ // (burns) need no escrow allowance: refunds never require maxData budget.
210
+ function defaultFreshRows(kind, count, romRamBytes) {
211
+ switch (kind) {
212
+ case NativeFeeKind.TransferFungible:
213
+ case NativeFeeKind.MintFungible:
214
+ return 1; // recipient balance row may be fresh (SOUL/KCAL rows would be free)
215
+ case NativeFeeKind.TransferNonFungible:
216
+ // Per instance the chain deletes the sender's NFT-lookup row and creates the recipient's
217
+ // (creation escrows at the current price, the deletion refunds the old row's own deposit)
218
+ // + possibly a fresh recipient balance row.
219
+ return count + 1;
220
+ case NativeFeeKind.MintNonFungible:
221
+ // Per instance: owner row + lookup row + the instance state rows holding ROM/RAM (1 KiB
222
+ // quanta), plus possibly a fresh recipient balance row.
223
+ return count * (2 + Math.ceil(romRamBytes / 1024)) + 1;
224
+ case NativeFeeKind.BurnFungible:
225
+ case NativeFeeKind.BurnNonFungible:
226
+ return 0;
227
+ case NativeFeeKind.CreateToken:
228
+ return 8; // token info + symbol lookup + supply/config rows, metadata-dependent
229
+ case NativeFeeKind.CreateTokenSeries:
230
+ return 4;
231
+ case NativeFeeKind.RegisterName:
232
+ return 2; // name->address and address->name rows
233
+ case NativeFeeKind.Script:
234
+ default:
235
+ return 4;
236
+ }
237
+ }
238
+ // Conservative single-witness envelope defaults per kind; used only when the caller did not
239
+ // measure the real signed size. Generous by design: under v2 an oversized estimate only raises
240
+ // the refunded offer, never the settled bill.
241
+ function defaultEnvelopeBytes(kind, count, romRamBytes) {
242
+ switch (kind) {
243
+ case NativeFeeKind.TransferFungible:
244
+ case NativeFeeKind.MintFungible:
245
+ case NativeFeeKind.BurnFungible:
246
+ case NativeFeeKind.RegisterName:
247
+ return 512;
248
+ case NativeFeeKind.TransferNonFungible:
249
+ case NativeFeeKind.BurnNonFungible:
250
+ return 512 + 8 * count; // 8 bytes per carried instance id
251
+ case NativeFeeKind.MintNonFungible:
252
+ return 512 + count * (64 + romRamBytes); // ROM/RAM ride the envelope
253
+ case NativeFeeKind.CreateToken:
254
+ return 4096; // token metadata (icons, descriptions) dominates
255
+ case NativeFeeKind.CreateTokenSeries:
256
+ return 2048;
257
+ case NativeFeeKind.Script:
258
+ default:
259
+ return 1024;
260
+ }
261
+ }
262
+ function roundUp(value, step) {
263
+ const rem = value % step;
264
+ return rem === 0n ? value : value + (step - rem);
265
+ }
266
+ function clampU64(value) {
267
+ return value > U64_MAX ? U64_MAX : value;
268
+ }
@@ -0,0 +1,61 @@
1
+ import { GasConfig } from '../../types/carbon/blockchain/gas-config.js';
2
+ /**
3
+ * Converts the getGasConfig JSON response to the wire-format GasConfig consumed by the Tier-1
4
+ * fee estimator. Throws on malformed numeric strings and on a v2 response missing tail fields:
5
+ * estimating fees from silently zeroed v2 prices would produce rejected transactions.
6
+ */
7
+ export function gasConfigFromRpc(result) {
8
+ const c = result.gasConfig;
9
+ if (!c) {
10
+ throw new Error('getGasConfig response has no gasConfig section');
11
+ }
12
+ const config = new GasConfig({
13
+ version: c.version,
14
+ maxNameLength: c.maxNameLength,
15
+ maxTokenSymbolLength: c.maxTokenSymbolLength,
16
+ feeShift: c.feeShift,
17
+ maxStructureSize: c.maxStructureSize,
18
+ feeMultiplier: parseU64(c.feeMultiplier, 'feeMultiplier'),
19
+ gasTokenId: parseU64(c.gasTokenId, 'gasTokenId'),
20
+ dataTokenId: parseU64(c.dataTokenId, 'dataTokenId'),
21
+ minimumGasOffer: parseU64(c.minimumGasOffer, 'minimumGasOffer'),
22
+ dataEscrowPerRow: parseU64(c.dataEscrowPerRow, 'dataEscrowPerRow'),
23
+ gasFeeTransfer: parseU64(c.gasFeeTransfer, 'gasFeeTransfer'),
24
+ gasFeeQuery: parseU64(c.gasFeeQuery, 'gasFeeQuery'),
25
+ gasFeeCreateTokenBase: parseU64(c.gasFeeCreateTokenBase, 'gasFeeCreateTokenBase'),
26
+ gasFeeCreateTokenSymbol: parseU64(c.gasFeeCreateTokenSymbol, 'gasFeeCreateTokenSymbol'),
27
+ gasFeeCreateTokenSeries: parseU64(c.gasFeeCreateTokenSeries, 'gasFeeCreateTokenSeries'),
28
+ gasFeePerByte: parseU64(c.gasFeePerByte, 'gasFeePerByte'),
29
+ gasFeeRegisterName: parseU64(c.gasFeeRegisterName, 'gasFeeRegisterName'),
30
+ gasBurnRatioMul: parseU64(c.gasBurnRatioMul, 'gasBurnRatioMul'),
31
+ gasBurnRatioShift: c.gasBurnRatioShift,
32
+ });
33
+ if (config.version >= 1) {
34
+ config.minimumGasBill = parseU64(c.minimumGasBill, 'minimumGasBill');
35
+ config.gasProducerRatioMul = parseU64(c.gasProducerRatioMul, 'gasProducerRatioMul');
36
+ config.gasProducerRatioShift = requireByte(c.gasProducerRatioShift, 'gasProducerRatioShift');
37
+ config.gasDappRatioMul = parseU64(c.gasDappRatioMul, 'gasDappRatioMul');
38
+ config.gasDappRatioShift = requireByte(c.gasDappRatioShift, 'gasDappRatioShift');
39
+ config.policyFeeCreateTokenBase = parseU64(c.policyFeeCreateTokenBase, 'policyFeeCreateTokenBase');
40
+ config.policyFeeCreateTokenSymbol = parseU64(c.policyFeeCreateTokenSymbol, 'policyFeeCreateTokenSymbol');
41
+ config.policyFeeCreateTokenSeries = parseU64(c.policyFeeCreateTokenSeries, 'policyFeeCreateTokenSeries');
42
+ config.policyFeeRegisterName = parseU64(c.policyFeeRegisterName, 'policyFeeRegisterName');
43
+ config.legacyDataEscrowPerRow = parseU64(c.legacyDataEscrowPerRow, 'legacyDataEscrowPerRow');
44
+ }
45
+ return config;
46
+ }
47
+ function parseU64(value, fieldName) {
48
+ if (value === undefined || value === '') {
49
+ throw new Error(`getGasConfig field ${fieldName} is missing or empty`);
50
+ }
51
+ if (!/^\d+$/.test(value)) {
52
+ throw new Error(`getGasConfig field ${fieldName} is not a decimal integer: ${value}`);
53
+ }
54
+ return BigInt(value);
55
+ }
56
+ function requireByte(value, fieldName) {
57
+ if (value === undefined) {
58
+ throw new Error(`getGasConfig field ${fieldName} is missing`);
59
+ }
60
+ return value;
61
+ }
@@ -14,6 +14,7 @@ export * from './contract.js';
14
14
  export * from './dapp.js';
15
15
  export * from './event.js';
16
16
  export * from './event-extended.js';
17
+ export * from './gas-config.js';
17
18
  export * from './governance.js';
18
19
  export * from './interop.js';
19
20
  export * from './key-value.js';
@@ -410,6 +410,16 @@ export class PhantasmaAPI {
410
410
  const params = [name, extended];
411
411
  return (await this.JSONRPC('getChain', params));
412
412
  }
413
+ /**
414
+ * Returns the current on-chain gas configuration plus the chain parameters fee estimation
415
+ * needs (block rate target, expiry window). Changes only via governance resolutions, so the
416
+ * result is safe to cache. Feed gasConfigFromRpc() into estimateNativeFee() for Tier-1 fee
417
+ * estimates.
418
+ */
419
+ async getGasConfig() {
420
+ const params = [];
421
+ return (await this.JSONRPC('getGasConfig', params));
422
+ }
413
423
  //Returns info about the nexus.
414
424
  // Warning: current Carbon RPC endpoint is stubbed and returns a default nexus object.
415
425
  async getNexus(extended = true) {