dkg.js 8.2.1 → 8.2.4
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/.github/workflows/check-package-lock.yml +75 -0
- package/constants/constants.js +6 -0
- package/index.cjs +372 -111
- package/managers/asset-operations-manager.js +141 -28
- package/package.json +2 -2
- package/services/blockchain-service/blockchain-service-base.js +161 -80
- package/services/blockchain-service/implementations/node-blockchain-service.js +36 -5
- package/services/input-service.js +20 -0
- package/services/node-api-service/implementations/http-service.js +16 -0
package/index.cjs
CHANGED
|
@@ -222,6 +222,11 @@ const GRAPH_STATES = {
|
|
|
222
222
|
HISTORICAL: 'HISTORICAL',
|
|
223
223
|
};
|
|
224
224
|
|
|
225
|
+
const GAS_MODES = {
|
|
226
|
+
LEGACY: 'legacy',
|
|
227
|
+
EIP1559: 'eip1559',
|
|
228
|
+
};
|
|
229
|
+
|
|
225
230
|
const QUERY_TYPES = {
|
|
226
231
|
CONSTRUCT: 'CONSTRUCT',
|
|
227
232
|
SELECT: 'SELECT',
|
|
@@ -274,6 +279,7 @@ const DEFAULT_PARAMETERS = {
|
|
|
274
279
|
FORCE_REPLACE_TXS: false,
|
|
275
280
|
GAS_LIMIT_MULTIPLIER: 1,
|
|
276
281
|
RETRY_TX_GAS_PRICE_MULTIPLIER: 3,
|
|
282
|
+
GAS_MODE: GAS_MODES.EIP1559,
|
|
277
283
|
};
|
|
278
284
|
|
|
279
285
|
const DEFAULT_GAS_PRICE = {
|
|
@@ -575,14 +581,13 @@ class AssetOperationsManager {
|
|
|
575
581
|
}
|
|
576
582
|
|
|
577
583
|
/**
|
|
578
|
-
*
|
|
584
|
+
* Phase 1 of asset creation: validate input, build dataset, and publish to the node.
|
|
579
585
|
* @async
|
|
580
|
-
* @param {Object} content - The content of the knowledge collection
|
|
581
|
-
* @param {Object} [options={}] -
|
|
582
|
-
* @
|
|
583
|
-
* @returns {Object} Object containing UAL, publicAssertionId and operation status.
|
|
586
|
+
* @param {Object|string} content - The content of the knowledge collection.
|
|
587
|
+
* @param {Object} [options={}] - Options for knowledge collection creation.
|
|
588
|
+
* @returns {Object} Publish phase output including dataset info and publish operation data.
|
|
584
589
|
*/
|
|
585
|
-
async
|
|
590
|
+
async publishAssetPhase(content, options = {}) {
|
|
586
591
|
this.validationService.validateJsonldOrNquads(content);
|
|
587
592
|
const {
|
|
588
593
|
blockchain,
|
|
@@ -735,17 +740,52 @@ class AssetOperationsManager {
|
|
|
735
740
|
publishOperationId,
|
|
736
741
|
);
|
|
737
742
|
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
743
|
+
return {
|
|
744
|
+
dataset,
|
|
745
|
+
datasetRoot,
|
|
746
|
+
datasetSize,
|
|
747
|
+
publishOperationId,
|
|
748
|
+
publishOperationResult,
|
|
749
|
+
contentAssetStorageAddress,
|
|
750
|
+
blockchain,
|
|
751
|
+
endpoint,
|
|
752
|
+
port,
|
|
753
|
+
maxNumberOfRetries,
|
|
754
|
+
frequency,
|
|
755
|
+
authToken,
|
|
756
|
+
epochsNum,
|
|
757
|
+
hashFunctionId,
|
|
758
|
+
scoreFunctionId,
|
|
759
|
+
immutable,
|
|
760
|
+
tokenAmount,
|
|
761
|
+
payer,
|
|
762
|
+
minimumNumberOfFinalizationConfirmations,
|
|
763
|
+
minimumNumberOfNodeReplications,
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* Phase 2 of asset creation: mint the knowledge collection on chain using publish output.
|
|
769
|
+
* @async
|
|
770
|
+
* @param {Object} publishPayload - Output of publishAssetPhase.
|
|
771
|
+
* @param {Object} [options={}] - Options affecting minting (e.g., minimumBlockConfirmations).
|
|
772
|
+
* @param {Object} [stepHooks=emptyHooks] - Hooks to execute during minting.
|
|
773
|
+
* @returns {Object} Mint phase output including UAL and mint receipt.
|
|
774
|
+
*/
|
|
775
|
+
async mintKnowledgeCollectionPhase(publishPayload, options = {}, stepHooks = emptyHooks$1) {
|
|
776
|
+
const {
|
|
777
|
+
dataset,
|
|
778
|
+
datasetRoot,
|
|
779
|
+
datasetSize,
|
|
780
|
+
publishOperationId,
|
|
781
|
+
publishOperationResult,
|
|
782
|
+
contentAssetStorageAddress,
|
|
783
|
+
blockchain,
|
|
784
|
+
epochsNum,
|
|
785
|
+
immutable,
|
|
786
|
+
tokenAmount,
|
|
787
|
+
payer,
|
|
788
|
+
} = publishPayload;
|
|
749
789
|
|
|
750
790
|
const { signatures } = publishOperationResult.data;
|
|
751
791
|
|
|
@@ -847,6 +887,45 @@ class AssetOperationsManager {
|
|
|
847
887
|
|
|
848
888
|
const UAL = deriveUAL$1(blockchain.name, contentAssetStorageAddress, knowledgeCollectionId);
|
|
849
889
|
|
|
890
|
+
return {
|
|
891
|
+
UAL,
|
|
892
|
+
knowledgeCollectionId,
|
|
893
|
+
mintKnowledgeCollectionReceipt,
|
|
894
|
+
datasetRoot,
|
|
895
|
+
publishOperationId,
|
|
896
|
+
publishOperationResult,
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* Phase 3 of asset creation: poll node finality status for the minted asset.
|
|
902
|
+
* @async
|
|
903
|
+
* @param {string} UAL - Universal Asset Locator returned from minting.
|
|
904
|
+
* @param {Object} [options={}] - Finality options.
|
|
905
|
+
* @returns {Object} Finality status details.
|
|
906
|
+
*/
|
|
907
|
+
async finalizePublishPhase(UAL, options = {}) {
|
|
908
|
+
// UAL should point to a knowledge collection (kcUAL), not a knowledge asset (kaUAL).
|
|
909
|
+
this.validationService.validateUAL(UAL);
|
|
910
|
+
|
|
911
|
+
const {
|
|
912
|
+
endpoint,
|
|
913
|
+
port,
|
|
914
|
+
maxNumberOfRetries,
|
|
915
|
+
frequency,
|
|
916
|
+
minimumNumberOfFinalizationConfirmations,
|
|
917
|
+
authToken,
|
|
918
|
+
} = this.inputService.getPublishFinalityArguments(options);
|
|
919
|
+
|
|
920
|
+
this.validationService.validatePublishFinality(
|
|
921
|
+
endpoint,
|
|
922
|
+
port,
|
|
923
|
+
maxNumberOfRetries,
|
|
924
|
+
frequency,
|
|
925
|
+
minimumNumberOfFinalizationConfirmations,
|
|
926
|
+
authToken,
|
|
927
|
+
);
|
|
928
|
+
|
|
850
929
|
let finalityStatusResult = 0;
|
|
851
930
|
if (minimumNumberOfFinalizationConfirmations > 0) {
|
|
852
931
|
finalityStatusResult = await this.nodeApiService.finalityStatus(
|
|
@@ -861,20 +940,60 @@ class AssetOperationsManager {
|
|
|
861
940
|
}
|
|
862
941
|
|
|
863
942
|
return {
|
|
864
|
-
|
|
865
|
-
|
|
943
|
+
status:
|
|
944
|
+
finalityStatusResult >= minimumNumberOfFinalizationConfirmations
|
|
945
|
+
? 'FINALIZED'
|
|
946
|
+
: 'NOT FINALIZED',
|
|
947
|
+
numberOfConfirmations: finalityStatusResult,
|
|
948
|
+
requiredConfirmations: minimumNumberOfFinalizationConfirmations,
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/**
|
|
953
|
+
* Creates a new knowledge collection.
|
|
954
|
+
* @async
|
|
955
|
+
* @param {Object} content - The content of the knowledge collection to be created, contains public, private or both keys.
|
|
956
|
+
* @param {Object} [options={}] - Additional options for knowledge collection creation.
|
|
957
|
+
* @param {Object} [stepHooks=emptyHooks] - Hooks to execute during knowledge collection creation.
|
|
958
|
+
* @returns {Object} Object containing UAL, publicAssertionId and operation status.
|
|
959
|
+
*/
|
|
960
|
+
async create(content, options = {}, stepHooks = emptyHooks$1) {
|
|
961
|
+
const publishOperationOutput = await this.publishAssetPhase(content, options);
|
|
962
|
+
const { datasetRoot, publishOperationId, publishOperationResult } = publishOperationOutput;
|
|
963
|
+
|
|
964
|
+
if (
|
|
965
|
+
publishOperationResult.status !== OPERATION_STATUSES$1.COMPLETED &&
|
|
966
|
+
!publishOperationResult.data.minAcksReached
|
|
967
|
+
) {
|
|
968
|
+
return {
|
|
969
|
+
datasetRoot,
|
|
970
|
+
operation: {
|
|
971
|
+
publish: getOperationStatusObject$1(publishOperationResult, publishOperationId),
|
|
972
|
+
},
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
const mintOperationOutput = await this.mintKnowledgeCollectionPhase(
|
|
977
|
+
publishOperationOutput,
|
|
978
|
+
options,
|
|
979
|
+
stepHooks,
|
|
980
|
+
);
|
|
981
|
+
|
|
982
|
+
const finalityOperationOutput = await this.finalizePublishPhase(
|
|
983
|
+
mintOperationOutput.UAL,
|
|
984
|
+
options,
|
|
985
|
+
);
|
|
986
|
+
|
|
987
|
+
return {
|
|
988
|
+
UAL: mintOperationOutput.UAL,
|
|
989
|
+
datasetRoot: mintOperationOutput.datasetRoot,
|
|
866
990
|
signatures: publishOperationResult.data.signatures,
|
|
867
991
|
operation: {
|
|
868
|
-
mintKnowledgeCollection: mintKnowledgeCollectionReceipt,
|
|
992
|
+
mintKnowledgeCollection: mintOperationOutput.mintKnowledgeCollectionReceipt,
|
|
869
993
|
publish: getOperationStatusObject$1(publishOperationResult, publishOperationId),
|
|
870
|
-
finality: {
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
? 'FINALIZED'
|
|
874
|
-
: 'NOT FINALIZED',
|
|
875
|
-
},
|
|
876
|
-
numberOfConfirmations: finalityStatusResult,
|
|
877
|
-
requiredConfirmations: minimumNumberOfFinalizationConfirmations,
|
|
994
|
+
finality: { status: finalityOperationOutput.status },
|
|
995
|
+
numberOfConfirmations: finalityOperationOutput.numberOfConfirmations,
|
|
996
|
+
requiredConfirmations: finalityOperationOutput.requiredConfirmations,
|
|
878
997
|
},
|
|
879
998
|
};
|
|
880
999
|
}
|
|
@@ -3113,6 +3232,22 @@ class HttpService {
|
|
|
3113
3232
|
|
|
3114
3233
|
return response.data.operationId;
|
|
3115
3234
|
} catch (error) {
|
|
3235
|
+
const status = error?.response?.status;
|
|
3236
|
+
const body = error?.response?.data;
|
|
3237
|
+
const url = `${this.getBaseUrl(endpoint, port)}/publish`;
|
|
3238
|
+
console.error(
|
|
3239
|
+
'Unable to publish',
|
|
3240
|
+
JSON.stringify(
|
|
3241
|
+
{
|
|
3242
|
+
url,
|
|
3243
|
+
status,
|
|
3244
|
+
body,
|
|
3245
|
+
message: error?.message,
|
|
3246
|
+
},
|
|
3247
|
+
null,
|
|
3248
|
+
2,
|
|
3249
|
+
),
|
|
3250
|
+
);
|
|
3116
3251
|
throw Error(`Unable to publish: ${error.message}`);
|
|
3117
3252
|
}
|
|
3118
3253
|
}
|
|
@@ -3510,63 +3645,66 @@ class BlockchainServiceBase {
|
|
|
3510
3645
|
);
|
|
3511
3646
|
gasLimit = Math.round(gasLimit * blockchain.gasLimitMultiplier);
|
|
3512
3647
|
|
|
3648
|
+
// Retry bumping is disabled by default. If you want to re-enable it, consider bumping
|
|
3649
|
+
// legacy gasPrice or EIP-1559 maxFeePerGas/maxPriorityFeePerGas with retryTxGasPriceMultiplier.
|
|
3650
|
+
// Example (legacy-only):
|
|
3513
3651
|
// let gasPrice;
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
} else {
|
|
3539
|
-
// If not found, use default/network gas price increased by retryTxGasPriceMultiplier
|
|
3540
|
-
// Theoretically this should never happen
|
|
3541
|
-
gasPrice = Math.round(
|
|
3542
|
-
(blockchain.gasPrice || (await this.getSmartGasPrice(blockchain))) * blockchain.retryTxGasPriceMultiplier,
|
|
3543
|
-
);
|
|
3544
|
-
}
|
|
3545
|
-
} else {
|
|
3546
|
-
gasPrice = blockchain.gasPrice || (await this.getSmartGasPrice(blockchain));
|
|
3547
|
-
}
|
|
3548
|
-
} else {
|
|
3549
|
-
gasPrice = blockchain.gasPrice || (await this.getSmartGasPrice(blockchain));
|
|
3550
|
-
}*/
|
|
3652
|
+
// if (blockchain.previousTxGasPrice && blockchain.retryTx) {
|
|
3653
|
+
// gasPrice = Math.round(blockchain.previousTxGasPrice * blockchain.retryTxGasPriceMultiplier);
|
|
3654
|
+
// } else if (blockchain.forceReplaceTxs) {
|
|
3655
|
+
// const currentNonce = await web3Instance.eth.getTransactionCount(publicKey, 'pending');
|
|
3656
|
+
// const confirmedNonce = await web3Instance.eth.getTransactionCount(publicKey, 'latest');
|
|
3657
|
+
// if (currentNonce > confirmedNonce) {
|
|
3658
|
+
// const pendingBlock = await web3Instance.eth.getBlock('pending', true);
|
|
3659
|
+
// const pendingTx = Object.values(pendingBlock.transactions).find(
|
|
3660
|
+
// (tx) => tx.from.toLowerCase() === publicKey.toLowerCase() && tx.nonce === confirmedNonce,
|
|
3661
|
+
// );
|
|
3662
|
+
// if (pendingTx) {
|
|
3663
|
+
// gasPrice = Math.round(Number(pendingTx.gasPrice) * blockchain.retryTxGasPriceMultiplier);
|
|
3664
|
+
// } else {
|
|
3665
|
+
// gasPrice = Math.round(
|
|
3666
|
+
// (blockchain.gasPrice || (await this.getGasPriceWeiWithFallback(blockchain))) *
|
|
3667
|
+
// blockchain.retryTxGasPriceMultiplier,
|
|
3668
|
+
// );
|
|
3669
|
+
// }
|
|
3670
|
+
// } else {
|
|
3671
|
+
// gasPrice = blockchain.gasPrice || (await this.getGasPriceWeiWithFallback(blockchain));
|
|
3672
|
+
// }
|
|
3673
|
+
// } else {
|
|
3674
|
+
// gasPrice = blockchain.gasPrice || (await this.getGasPriceWeiWithFallback(blockchain));
|
|
3675
|
+
// }
|
|
3551
3676
|
|
|
3552
|
-
const
|
|
3677
|
+
const gasFeeOptions = await this.getGasFeeOptions(blockchain);
|
|
3553
3678
|
|
|
3554
3679
|
if (blockchain.simulateTxs) {
|
|
3555
|
-
|
|
3680
|
+
const simulationTx = {
|
|
3556
3681
|
to: contractInstance.options.address,
|
|
3557
3682
|
data: encodedABI,
|
|
3558
3683
|
from: publicKey,
|
|
3559
|
-
gasPrice,
|
|
3560
3684
|
gas: gasLimit,
|
|
3561
|
-
}
|
|
3685
|
+
};
|
|
3686
|
+
|
|
3687
|
+
if (gasFeeOptions.type === GAS_MODES.EIP1559) {
|
|
3688
|
+
simulationTx.maxFeePerGas = gasFeeOptions.maxFeePerGas;
|
|
3689
|
+
simulationTx.maxPriorityFeePerGas = gasFeeOptions.maxPriorityFeePerGas;
|
|
3690
|
+
} else {
|
|
3691
|
+
simulationTx.gasPrice = gasFeeOptions.gasPrice;
|
|
3692
|
+
}
|
|
3693
|
+
|
|
3694
|
+
await web3Instance.eth.call(simulationTx);
|
|
3562
3695
|
}
|
|
3563
3696
|
|
|
3564
3697
|
return {
|
|
3565
3698
|
from: publicKey,
|
|
3566
3699
|
to: contractInstance.options.address,
|
|
3567
3700
|
data: encodedABI,
|
|
3568
|
-
gasPrice,
|
|
3569
3701
|
gas: gasLimit,
|
|
3702
|
+
...(gasFeeOptions.type === GAS_MODES.EIP1559
|
|
3703
|
+
? {
|
|
3704
|
+
maxFeePerGas: gasFeeOptions.maxFeePerGas,
|
|
3705
|
+
maxPriorityFeePerGas: gasFeeOptions.maxPriorityFeePerGas,
|
|
3706
|
+
}
|
|
3707
|
+
: { gasPrice: gasFeeOptions.gasPrice }),
|
|
3570
3708
|
};
|
|
3571
3709
|
}
|
|
3572
3710
|
|
|
@@ -3830,6 +3968,25 @@ class BlockchainServiceBase {
|
|
|
3830
3968
|
if (requestData?.paymaster && requestData?.paymaster !== ZERO_ADDRESS) {
|
|
3831
3969
|
// Handle the case when payer is passed
|
|
3832
3970
|
} else {
|
|
3971
|
+
const senderBalance = await this.callContractFunction(
|
|
3972
|
+
'Token',
|
|
3973
|
+
'balanceOf',
|
|
3974
|
+
[sender],
|
|
3975
|
+
blockchain,
|
|
3976
|
+
);
|
|
3977
|
+
|
|
3978
|
+
if (BigInt(senderBalance) < BigInt(requestData.tokenAmount)) {
|
|
3979
|
+
const balance = Number(senderBalance) / 1e18;
|
|
3980
|
+
const required = Number(requestData.tokenAmount) / 1e18;
|
|
3981
|
+
|
|
3982
|
+
throw new Error(
|
|
3983
|
+
`Insufficient TRAC token balance to publish. ` +
|
|
3984
|
+
`Wallet ${sender} has ${balance} TRAC, ` +
|
|
3985
|
+
`but the publish operation requires ${required} TRAC. ` +
|
|
3986
|
+
`Please fund your wallet with more TRAC tokens to proceed.`,
|
|
3987
|
+
);
|
|
3988
|
+
}
|
|
3989
|
+
|
|
3833
3990
|
await this.increaseKnowledgeCollectionAllowance(
|
|
3834
3991
|
sender,
|
|
3835
3992
|
requestData.tokenAmount,
|
|
@@ -4727,7 +4884,10 @@ class BlockchainServiceBase {
|
|
|
4727
4884
|
try {
|
|
4728
4885
|
// eth_feeHistory params: blockCount, newestBlock, rewardPercentiles
|
|
4729
4886
|
// [50] = median priority fee per block
|
|
4730
|
-
const
|
|
4887
|
+
const priorityFeePercentile = blockchain.priorityFeePercentile ?? 80;
|
|
4888
|
+
const feeHistory = await web3Instance.eth.getFeeHistory(blockCount, 'latest', [
|
|
4889
|
+
priorityFeePercentile,
|
|
4890
|
+
]);
|
|
4731
4891
|
|
|
4732
4892
|
// Extract median priority fees from each block (reward[blockIndex][percentileIndex])
|
|
4733
4893
|
const priorityFees = feeHistory.reward
|
|
@@ -4751,61 +4911,62 @@ class BlockchainServiceBase {
|
|
|
4751
4911
|
|
|
4752
4912
|
/**
|
|
4753
4913
|
* Apply buffer percentage to a gas price
|
|
4754
|
-
* @param {BigInt}
|
|
4914
|
+
* @param {BigInt} maxBaseFee - base fee in wei
|
|
4915
|
+
* @param {BigInt} maxPriorityFee - priority fee in wei
|
|
4755
4916
|
* @param {number} gasPriceBufferPercent - Buffer percentage to add
|
|
4756
4917
|
* @returns {BigInt} Gas price with buffer applied
|
|
4757
4918
|
*/
|
|
4758
|
-
applyGasPriceBuffer(
|
|
4759
|
-
if (!gasPriceBufferPercent) return
|
|
4760
|
-
return (
|
|
4919
|
+
applyGasPriceBuffer(maxBaseFee, maxPriorityFee, gasPriceBufferPercent) {
|
|
4920
|
+
if (!gasPriceBufferPercent) return maxBaseFee + maxPriorityFee;
|
|
4921
|
+
return (maxBaseFee * BigInt(100 + Number(gasPriceBufferPercent))) / 100n + maxPriorityFee;
|
|
4922
|
+
}
|
|
4923
|
+
|
|
4924
|
+
buildEip1559FeesFromHistory(feeHistory, gasPriceBufferPercent = 0) {
|
|
4925
|
+
const baseFees = Array.from(feeHistory.baseFeePerGas ?? []);
|
|
4926
|
+
const priorityFees = Array.from(feeHistory.priorityFees ?? []);
|
|
4927
|
+
|
|
4928
|
+
if (baseFees.length === 0 || priorityFees.length === 0) {
|
|
4929
|
+
throw new Error('Fee history data is empty');
|
|
4930
|
+
}
|
|
4931
|
+
|
|
4932
|
+
const maxBaseFee = baseFees.reduce((max, bf) => (bf > max ? bf : max), 0n);
|
|
4933
|
+
const maxPriorityFeePerGas = priorityFees.reduce((max, pf) => (pf > max ? pf : max), 0n);
|
|
4934
|
+
|
|
4935
|
+
const maxFeePerGas = this.applyGasPriceBuffer(
|
|
4936
|
+
maxBaseFee,
|
|
4937
|
+
maxPriorityFeePerGas,
|
|
4938
|
+
gasPriceBufferPercent,
|
|
4939
|
+
);
|
|
4940
|
+
|
|
4941
|
+
return {
|
|
4942
|
+
maxFeePerGas,
|
|
4943
|
+
maxPriorityFeePerGas,
|
|
4944
|
+
};
|
|
4761
4945
|
}
|
|
4762
4946
|
|
|
4763
4947
|
/**
|
|
4764
|
-
* Estimate safe gas
|
|
4765
|
-
* Takes max base fee from last N blocks, adds a buffer for volatility,
|
|
4766
|
-
* and includes the priority fee (tip) for validator incentive
|
|
4948
|
+
* Estimate safe gas fees using eth_feeHistory (EIP-1559 style)
|
|
4767
4949
|
* @param {Object} blockchain - Blockchain configuration
|
|
4768
|
-
* @returns {Promise<
|
|
4950
|
+
* @returns {Promise<{maxFeePerGas: bigint, maxPriorityFeePerGas: bigint}>}
|
|
4769
4951
|
*/
|
|
4770
|
-
async
|
|
4771
|
-
const { gasPriceBufferPercent } = blockchain;
|
|
4952
|
+
async estimateEip1559Fees(blockchain) {
|
|
4772
4953
|
const feeHistory = await this.getFeeHistory(blockchain, FEE_HISTORY_BLOCK_COUNT);
|
|
4773
|
-
|
|
4774
|
-
// Fallback to network gas price if feeHistory not supported or empty
|
|
4775
4954
|
if (!feeHistory.supported) {
|
|
4776
|
-
|
|
4777
|
-
BigInt(await this.getNetworkGasPrice(blockchain)),
|
|
4778
|
-
gasPriceBufferPercent,
|
|
4779
|
-
);
|
|
4780
|
-
}
|
|
4781
|
-
|
|
4782
|
-
const baseFees = Array.from(feeHistory.baseFeePerGas);
|
|
4783
|
-
const priorityFees = Array.from(feeHistory.priorityFees);
|
|
4784
|
-
|
|
4785
|
-
if (baseFees.length === 0 || priorityFees.length === 0) {
|
|
4786
|
-
return this.applyGasPriceBuffer(
|
|
4787
|
-
BigInt(await this.getNetworkGasPrice(blockchain)),
|
|
4788
|
-
gasPriceBufferPercent,
|
|
4789
|
-
);
|
|
4955
|
+
throw new Error('eth_feeHistory not supported');
|
|
4790
4956
|
}
|
|
4791
4957
|
|
|
4792
|
-
|
|
4793
|
-
const maxBaseFee = baseFees.reduce((max, bf) => (bf > max ? bf : max), 0n);
|
|
4794
|
-
const maxPriorityFee = priorityFees.reduce((max, pf) => (pf > max ? pf : max), 0n);
|
|
4795
|
-
|
|
4796
|
-
return this.applyGasPriceBuffer(maxBaseFee + maxPriorityFee, gasPriceBufferPercent);
|
|
4958
|
+
return this.buildEip1559FeesFromHistory(feeHistory, blockchain.gasPriceBufferPercent ?? 0);
|
|
4797
4959
|
}
|
|
4798
4960
|
|
|
4799
4961
|
/**
|
|
4800
|
-
* Get gas price
|
|
4801
|
-
* Tries eth_feeHistory first, falls back to legacy methods
|
|
4962
|
+
* Get preferred gas price in wei: try EIP-1559 fee history, fall back to legacy network gas price.
|
|
4802
4963
|
* @param {Object} blockchain - Blockchain configuration
|
|
4803
4964
|
* @returns {Promise<string>} Gas price in wei (as string for web3 compatibility)
|
|
4804
4965
|
*/
|
|
4805
|
-
async
|
|
4966
|
+
async getGasPriceWeiWithFallback(blockchain) {
|
|
4806
4967
|
try {
|
|
4807
|
-
const
|
|
4808
|
-
return
|
|
4968
|
+
const { maxFeePerGas } = await this.estimateEip1559Fees(blockchain);
|
|
4969
|
+
return maxFeePerGas.toString();
|
|
4809
4970
|
} catch (eip1559Error) {
|
|
4810
4971
|
try {
|
|
4811
4972
|
return await this.getNetworkGasPrice(blockchain);
|
|
@@ -4819,6 +4980,59 @@ class BlockchainServiceBase {
|
|
|
4819
4980
|
}
|
|
4820
4981
|
}
|
|
4821
4982
|
|
|
4983
|
+
normalizeGasMode(gasMode) {
|
|
4984
|
+
const requested = (gasMode || '').toLowerCase();
|
|
4985
|
+
if (Object.values(GAS_MODES).includes(requested)) {
|
|
4986
|
+
return requested;
|
|
4987
|
+
}
|
|
4988
|
+
return DEFAULT_PARAMETERS.GAS_MODE;
|
|
4989
|
+
}
|
|
4990
|
+
|
|
4991
|
+
/**
|
|
4992
|
+
* Resolve gas fee fields based on configured gas mode and network support
|
|
4993
|
+
* @param {Object} blockchain - Blockchain configuration
|
|
4994
|
+
* @returns {Promise<Object>} Gas fee fields to merge into tx (legacy or EIP-1559)
|
|
4995
|
+
*/
|
|
4996
|
+
async getGasFeeOptions(blockchain) {
|
|
4997
|
+
const desiredMode = this.normalizeGasMode(blockchain.gasMode);
|
|
4998
|
+
const feeHistory = await this.getFeeHistory(blockchain, FEE_HISTORY_BLOCK_COUNT);
|
|
4999
|
+
const supportsEip1559 =
|
|
5000
|
+
feeHistory.supported &&
|
|
5001
|
+
feeHistory.baseFeePerGas?.length &&
|
|
5002
|
+
feeHistory.priorityFees?.length;
|
|
5003
|
+
|
|
5004
|
+
if (desiredMode === GAS_MODES.EIP1559 && supportsEip1559) {
|
|
5005
|
+
const { maxFeePerGas, maxPriorityFeePerGas } = this.buildEip1559FeesFromHistory(
|
|
5006
|
+
feeHistory,
|
|
5007
|
+
blockchain.gasPriceBufferPercent ?? 0,
|
|
5008
|
+
);
|
|
5009
|
+
|
|
5010
|
+
return {
|
|
5011
|
+
type: GAS_MODES.EIP1559,
|
|
5012
|
+
maxFeePerGas: maxFeePerGas.toString(),
|
|
5013
|
+
maxPriorityFeePerGas: maxPriorityFeePerGas.toString(),
|
|
5014
|
+
};
|
|
5015
|
+
}
|
|
5016
|
+
|
|
5017
|
+
if (desiredMode === GAS_MODES.EIP1559 && !supportsEip1559) {
|
|
5018
|
+
// eslint-disable-next-line no-console
|
|
5019
|
+
console.warn(
|
|
5020
|
+
'EIP-1559 gas mode requested but eth_feeHistory is unsupported; skipping feeHistory retry and falling back to legacy gasPrice',
|
|
5021
|
+
);
|
|
5022
|
+
}
|
|
5023
|
+
|
|
5024
|
+
const legacyGasPrice =
|
|
5025
|
+
blockchain.gasPrice ??
|
|
5026
|
+
(supportsEip1559
|
|
5027
|
+
? await this.getGasPriceWeiWithFallback(blockchain)
|
|
5028
|
+
: await this.getNetworkGasPrice(blockchain));
|
|
5029
|
+
|
|
5030
|
+
return {
|
|
5031
|
+
type: GAS_MODES.LEGACY,
|
|
5032
|
+
gasPrice: legacyGasPrice?.toString?.() ?? legacyGasPrice,
|
|
5033
|
+
};
|
|
5034
|
+
}
|
|
5035
|
+
|
|
4822
5036
|
async getWalletBalances(blockchain) {
|
|
4823
5037
|
await this.ensureBlockchainInfo(blockchain);
|
|
4824
5038
|
const web3Instance = await this.getWeb3Instance(blockchain);
|
|
@@ -5051,6 +5265,8 @@ class NodeBlockchainService extends BlockchainServiceBase {
|
|
|
5051
5265
|
};
|
|
5052
5266
|
},
|
|
5053
5267
|
);
|
|
5268
|
+
|
|
5269
|
+
this.nextNonces = new Map();
|
|
5054
5270
|
}
|
|
5055
5271
|
|
|
5056
5272
|
initializeWeb3(blockchainName, blockchainRpc, blockchainOptions) {
|
|
@@ -5090,13 +5306,30 @@ class NodeBlockchainService extends BlockchainServiceBase {
|
|
|
5090
5306
|
return blockchain?.publicKey;
|
|
5091
5307
|
}
|
|
5092
5308
|
|
|
5309
|
+
async allocateNonce(blockchain) {
|
|
5310
|
+
const address = (await this.getPublicKey(blockchain))?.toLowerCase();
|
|
5311
|
+
if (!address) throw new Error('Missing public key for nonce allocation');
|
|
5312
|
+
|
|
5313
|
+
if (!this.nextNonces.has(address)) {
|
|
5314
|
+
const web3Instance = await this.getWeb3Instance(blockchain);
|
|
5315
|
+
// Seed the local nonce tracker from the pending nonce to avoid collisions across sequential txs.
|
|
5316
|
+
const startingNonce = await web3Instance.eth.getTransactionCount(address, 'pending');
|
|
5317
|
+
this.nextNonces.set(address, startingNonce);
|
|
5318
|
+
}
|
|
5319
|
+
|
|
5320
|
+
const nonce = this.nextNonces.get(address);
|
|
5321
|
+
// Increment locally so concurrent sends reuse the monotonic nonce without extra RPC calls.
|
|
5322
|
+
this.nextNonces.set(address, nonce + 1);
|
|
5323
|
+
return nonce;
|
|
5324
|
+
}
|
|
5325
|
+
|
|
5093
5326
|
async executeContractFunction(contractName, functionName, args, blockchain) {
|
|
5094
5327
|
await this.ensureBlockchainInfo(blockchain);
|
|
5095
5328
|
const web3Instance = await this.getWeb3Instance(blockchain);
|
|
5096
5329
|
let contractInstance = await this.getContractInstance(contractName, blockchain);
|
|
5097
5330
|
|
|
5098
5331
|
let receipt;
|
|
5099
|
-
let
|
|
5332
|
+
let lastSentGasPrice;
|
|
5100
5333
|
let simulationSucceeded = false;
|
|
5101
5334
|
let transactionRetried = false;
|
|
5102
5335
|
|
|
@@ -5108,17 +5341,25 @@ class NodeBlockchainService extends BlockchainServiceBase {
|
|
|
5108
5341
|
args,
|
|
5109
5342
|
blockchain,
|
|
5110
5343
|
);
|
|
5111
|
-
|
|
5344
|
+
const nonce = await this.allocateNonce(blockchain);
|
|
5345
|
+
// Track what we sent in case we need to retry without a receipt.
|
|
5346
|
+
lastSentGasPrice = tx.gasPrice ?? tx.maxFeePerGas;
|
|
5112
5347
|
simulationSucceeded = true;
|
|
5113
5348
|
|
|
5114
5349
|
const createdTransaction = await web3Instance.eth.accounts.signTransaction(
|
|
5115
|
-
tx,
|
|
5350
|
+
{ ...tx, nonce },
|
|
5116
5351
|
blockchain.privateKey,
|
|
5117
5352
|
);
|
|
5118
5353
|
|
|
5119
5354
|
receipt = await web3Instance.eth.sendSignedTransaction(
|
|
5120
5355
|
createdTransaction.rawTransaction,
|
|
5121
5356
|
);
|
|
5357
|
+
|
|
5358
|
+
const actualGasPrice =
|
|
5359
|
+
receipt?.effectiveGasPrice ?? receipt?.gasPrice ?? lastSentGasPrice;
|
|
5360
|
+
lastSentGasPrice = actualGasPrice;
|
|
5361
|
+
blockchain.previousTxGasPrice = actualGasPrice;
|
|
5362
|
+
|
|
5122
5363
|
if (blockchain.name.startsWith('otp') && blockchain.waitNeurowebTxFinalization) {
|
|
5123
5364
|
receipt = await this.waitForTransactionFinalization(receipt, blockchain);
|
|
5124
5365
|
}
|
|
@@ -5133,7 +5374,8 @@ class NodeBlockchainService extends BlockchainServiceBase {
|
|
|
5133
5374
|
) {
|
|
5134
5375
|
transactionRetried = true;
|
|
5135
5376
|
blockchain.retryTx = true;
|
|
5136
|
-
|
|
5377
|
+
// Prefer actual paid price; fall back to what we sent if no receipt.
|
|
5378
|
+
blockchain.previousTxGasPrice = lastSentGasPrice;
|
|
5137
5379
|
} else if (!transactionRetried && /revert|VM Exception/i.test(error.message)) {
|
|
5138
5380
|
let status;
|
|
5139
5381
|
try {
|
|
@@ -6231,12 +6473,29 @@ class InputService {
|
|
|
6231
6473
|
BLOCKCHAINS[environment][name]?.gasPriceOracleLink ??
|
|
6232
6474
|
undefined;
|
|
6233
6475
|
|
|
6476
|
+
const getEnvGasMode = () =>
|
|
6477
|
+
typeof process !== 'undefined' && process?.env ? process.env.DKG_GAS_MODE : undefined;
|
|
6478
|
+
|
|
6479
|
+
const requestedGasMode =
|
|
6480
|
+
options.blockchain?.gasMode ??
|
|
6481
|
+
this.config.blockchain?.gasMode ??
|
|
6482
|
+
getEnvGasMode() ??
|
|
6483
|
+
DEFAULT_PARAMETERS.GAS_MODE;
|
|
6484
|
+
const normalizedRequestedGasMode = (requestedGasMode || '').toLowerCase();
|
|
6485
|
+
const normalizedGasMode = Object.values(GAS_MODES).includes(normalizedRequestedGasMode)
|
|
6486
|
+
? normalizedRequestedGasMode
|
|
6487
|
+
: DEFAULT_PARAMETERS.GAS_MODE;
|
|
6488
|
+
|
|
6234
6489
|
const maxAllowance =
|
|
6235
6490
|
options.blockchain?.maxAllowance ?? this.config.blockchain?.maxAllowance ?? undefined;
|
|
6236
6491
|
const gasPriceBufferPercent =
|
|
6237
6492
|
options.blockchain?.gasPriceBufferPercent ??
|
|
6238
6493
|
this.config.blockchain?.gasPriceBufferPercent ??
|
|
6239
6494
|
undefined;
|
|
6495
|
+
const priorityFeePercentile =
|
|
6496
|
+
options.blockchain?.priorityFeePercentile ??
|
|
6497
|
+
this.config.blockchain?.priorityFeePercentile ??
|
|
6498
|
+
undefined;
|
|
6240
6499
|
const retryTxGasPriceMultiplier =
|
|
6241
6500
|
options.blockchain?.retryTxGasPriceMultiplier ??
|
|
6242
6501
|
this.config.blockchain?.retryTxGasPriceMultiplier ??
|
|
@@ -6255,8 +6514,10 @@ class InputService {
|
|
|
6255
6514
|
simulateTxs,
|
|
6256
6515
|
forceReplaceTxs,
|
|
6257
6516
|
gasPriceOracleLink,
|
|
6517
|
+
gasMode: normalizedGasMode,
|
|
6258
6518
|
maxAllowance,
|
|
6259
6519
|
gasPriceBufferPercent,
|
|
6520
|
+
priorityFeePercentile,
|
|
6260
6521
|
retryTxGasPriceMultiplier,
|
|
6261
6522
|
};
|
|
6262
6523
|
|