@stellar/stellar-base 14.0.3 → 14.1.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/README.md +13 -15
- package/dist/stellar-base.js +2348 -2186
- package/dist/stellar-base.min.js +1 -1
- package/lib/address.js +4 -1
- package/lib/auth.js +2 -4
- package/lib/soroban.js +6 -4
- package/lib/transaction_builder.js +161 -3
- package/package.json +8 -8
- package/types/curr.d.ts +121 -146
- package/types/index.d.ts +24 -0
- package/types/next.d.ts +121 -145
- package/CHANGELOG.md +0 -1489
package/lib/address.js
CHANGED
|
@@ -228,7 +228,10 @@ var Address = exports.Address = /*#__PURE__*/function () {
|
|
|
228
228
|
return Address.muxedAccount(raw);
|
|
229
229
|
}
|
|
230
230
|
case _xdr["default"].ScAddressType.scAddressTypeClaimableBalance().value:
|
|
231
|
-
|
|
231
|
+
{
|
|
232
|
+
var cbi = scAddress.claimableBalanceId();
|
|
233
|
+
return Address.claimableBalance(Buffer.concat([Buffer.from([cbi["switch"]().value]), cbi.v0()]));
|
|
234
|
+
}
|
|
232
235
|
case _xdr["default"].ScAddressType.scAddressTypeLiquidityPool().value:
|
|
233
236
|
return Address.liquidityPool(scAddress.liquidityPoolId());
|
|
234
237
|
default:
|
package/lib/auth.js
CHANGED
|
@@ -26,10 +26,8 @@ function _asyncToGenerator(n) { return function () { var t = this, e = arguments
|
|
|
26
26
|
* whose hash you should sign, so that you can inspect the entire structure
|
|
27
27
|
* if necessary (rather than blindly signing a hash)
|
|
28
28
|
*
|
|
29
|
-
* @returns {
|
|
30
|
-
*
|
|
31
|
-
* Promise<{signature: Uint8Array, publicKey: string}
|
|
32
|
-
* } the signature of the raw payload (which is the sha256 hash of the preimage
|
|
29
|
+
* @returns {Promise<Uint8Array | {signature: Uint8Array, publicKey: string}>}
|
|
30
|
+
* the signature of the raw payload (which is the sha256 hash of the preimage
|
|
33
31
|
* bytes, so `hash(preimage.toXDR())`) either naked, implying it is signed
|
|
34
32
|
* by the key corresponding to the public key in the entry you pass to
|
|
35
33
|
* {@link authorizeEntry} (decipherable from its
|
package/lib/soroban.js
CHANGED
|
@@ -38,6 +38,8 @@ var Soroban = exports.Soroban = /*#__PURE__*/function () {
|
|
|
38
38
|
* @throws {TypeError} if the given amount has a decimal point already
|
|
39
39
|
* @example
|
|
40
40
|
* formatTokenAmount("123000", 4) === "12.3";
|
|
41
|
+
* formatTokenAmount("123000", 3) === "123.0";
|
|
42
|
+
* formatTokenAmount("123", 3) === "0.123";
|
|
41
43
|
*/
|
|
42
44
|
function formatTokenAmount(amount, decimals) {
|
|
43
45
|
if (amount.includes('.')) {
|
|
@@ -51,9 +53,9 @@ var Soroban = exports.Soroban = /*#__PURE__*/function () {
|
|
|
51
53
|
formatted = [formatted.slice(0, -decimals), formatted.slice(-decimals)].join('.');
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
|
-
|
|
55
|
-
//
|
|
56
|
-
|
|
56
|
+
return formatted.replace(/(\.\d*?)0+$/, '$1') // strip trailing zeroes
|
|
57
|
+
.replace(/\.$/, '.0') // but keep at least one
|
|
58
|
+
.replace(/^\./, '0.'); // and a leading one
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
/**
|
|
@@ -84,7 +86,7 @@ var Soroban = exports.Soroban = /*#__PURE__*/function () {
|
|
|
84
86
|
_value$split$slice2 = _toArray(_value$split$slice),
|
|
85
87
|
whole = _value$split$slice2[0],
|
|
86
88
|
fraction = _value$split$slice2[1],
|
|
87
|
-
rest = _value$split$slice2.slice(2);
|
|
89
|
+
rest = _arrayLikeToArray(_value$split$slice2).slice(2);
|
|
88
90
|
if (rest.length) {
|
|
89
91
|
throw new Error("Invalid decimal value: ".concat(value));
|
|
90
92
|
}
|
|
@@ -17,6 +17,11 @@ var _sorobandata_builder = require("./sorobandata_builder");
|
|
|
17
17
|
var _strkey = require("./strkey");
|
|
18
18
|
var _signerkey = require("./signerkey");
|
|
19
19
|
var _memo = require("./memo");
|
|
20
|
+
var _asset = require("./asset");
|
|
21
|
+
var _scval = require("./scval");
|
|
22
|
+
var _operation = require("./operation");
|
|
23
|
+
var _address = require("./address");
|
|
24
|
+
var _keypair = require("./keypair");
|
|
20
25
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
|
|
21
26
|
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
22
27
|
function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
|
|
@@ -32,7 +37,7 @@ function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Can
|
|
|
32
37
|
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
|
|
33
38
|
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
|
|
34
39
|
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
|
|
35
|
-
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
40
|
+
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } // eslint-disable-next-line no-unused-vars
|
|
36
41
|
/**
|
|
37
42
|
* Minimum base fee for transactions. If this fee is below the network
|
|
38
43
|
* minimum, the transaction will fail. The more operations in the
|
|
@@ -52,6 +57,14 @@ var BASE_FEE = exports.BASE_FEE = '100'; // Stroops
|
|
|
52
57
|
*/
|
|
53
58
|
var TimeoutInfinite = exports.TimeoutInfinite = 0;
|
|
54
59
|
|
|
60
|
+
/**
|
|
61
|
+
* @typedef {object} SorobanFees
|
|
62
|
+
* @property {number} instructions - the number of instructions executed by the transaction
|
|
63
|
+
* @property {number} readBytes - the number of bytes read from the ledger by the transaction
|
|
64
|
+
* @property {number} writeBytes - the number of bytes written to the ledger by the transaction
|
|
65
|
+
* @property {bigint} resourceFee - the fee to be paid for the transaction, in stroops
|
|
66
|
+
*/
|
|
67
|
+
|
|
55
68
|
/**
|
|
56
69
|
* <p>Transaction builder helps constructs a new `{@link Transaction}` using the
|
|
57
70
|
* given {@link Account} as the transaction's "source account". The transaction
|
|
@@ -554,6 +567,150 @@ var TransactionBuilder = exports.TransactionBuilder = /*#__PURE__*/function () {
|
|
|
554
567
|
return this;
|
|
555
568
|
}
|
|
556
569
|
|
|
570
|
+
/**
|
|
571
|
+
* Creates and adds an invoke host function operation for transferring SAC tokens.
|
|
572
|
+
* This method removes the need for simulation by handling the creation of the
|
|
573
|
+
* appropriate authorization entries and ledger footprint for the transfer operation.
|
|
574
|
+
*
|
|
575
|
+
* @param {string} destination - the address of the recipient of the SAC transfer (should be a valid Stellar address or contract ID)
|
|
576
|
+
* @param {Asset} asset - the SAC asset to be transferred
|
|
577
|
+
* @param {BigInt} amount - the amount of tokens to be transferred in 7 decimals. IE 1 token with 7 decimals of precision would be represented as "1_0000000"
|
|
578
|
+
* @param {SorobanFees} [sorobanFees] - optional Soroban fees for the transaction to override the default fees used
|
|
579
|
+
*
|
|
580
|
+
* @returns {TransactionBuilder}
|
|
581
|
+
*/
|
|
582
|
+
}, {
|
|
583
|
+
key: "addSacTransferOperation",
|
|
584
|
+
value: function addSacTransferOperation(destination, asset, amount, sorobanFees) {
|
|
585
|
+
if (BigInt(amount) <= 0n) {
|
|
586
|
+
throw new Error('Amount must be a positive integer');
|
|
587
|
+
} else if (BigInt(amount) > _jsXdr.Hyper.MAX_VALUE) {
|
|
588
|
+
// The largest supported value for SAC is i64 however the contract interface uses i128 which is why we convert it to i128
|
|
589
|
+
throw new Error('Amount exceeds maximum value for i64');
|
|
590
|
+
}
|
|
591
|
+
if (sorobanFees) {
|
|
592
|
+
var instructions = sorobanFees.instructions,
|
|
593
|
+
readBytes = sorobanFees.readBytes,
|
|
594
|
+
writeBytes = sorobanFees.writeBytes,
|
|
595
|
+
resourceFee = sorobanFees.resourceFee;
|
|
596
|
+
var U32_MAX = 4294967295;
|
|
597
|
+
if (instructions <= 0 || instructions > U32_MAX) {
|
|
598
|
+
throw new Error("instructions must be greater than 0 and at most ".concat(U32_MAX));
|
|
599
|
+
}
|
|
600
|
+
if (readBytes <= 0 || readBytes > U32_MAX) {
|
|
601
|
+
throw new Error("readBytes must be greater than 0 and at most ".concat(U32_MAX));
|
|
602
|
+
}
|
|
603
|
+
if (writeBytes <= 0 || writeBytes > U32_MAX) {
|
|
604
|
+
throw new Error("writeBytes must be greater than 0 and at most ".concat(U32_MAX));
|
|
605
|
+
}
|
|
606
|
+
if (resourceFee <= 0n || resourceFee > _jsXdr.Hyper.MAX_VALUE) {
|
|
607
|
+
throw new Error('resourceFee must be greater than 0 and at most i64 max');
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
var isDestinationContract = _strkey.StrKey.isValidContract(destination);
|
|
611
|
+
if (!isDestinationContract) {
|
|
612
|
+
if (!_strkey.StrKey.isValidEd25519PublicKey(destination) && !_strkey.StrKey.isValidMed25519PublicKey(destination)) {
|
|
613
|
+
throw new Error('Invalid destination address. Must be a valid Stellar address or contract ID.');
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
if (destination === this.source.accountId()) {
|
|
617
|
+
throw new Error('Destination cannot be the same as the source account.');
|
|
618
|
+
}
|
|
619
|
+
var contractId = asset.contractId(this.networkPassphrase);
|
|
620
|
+
var functionName = 'transfer';
|
|
621
|
+
var source = this.source.accountId();
|
|
622
|
+
var args = [(0, _scval.nativeToScVal)(source, {
|
|
623
|
+
type: 'address'
|
|
624
|
+
}), (0, _scval.nativeToScVal)(destination, {
|
|
625
|
+
type: 'address'
|
|
626
|
+
}), (0, _scval.nativeToScVal)(amount, {
|
|
627
|
+
type: 'i128'
|
|
628
|
+
})];
|
|
629
|
+
var isAssetNative = asset.isNative();
|
|
630
|
+
var auths = new _xdr["default"].SorobanAuthorizationEntry({
|
|
631
|
+
credentials: _xdr["default"].SorobanCredentials.sorobanCredentialsSourceAccount(),
|
|
632
|
+
rootInvocation: new _xdr["default"].SorobanAuthorizedInvocation({
|
|
633
|
+
"function": _xdr["default"].SorobanAuthorizedFunction.sorobanAuthorizedFunctionTypeContractFn(new _xdr["default"].InvokeContractArgs({
|
|
634
|
+
contractAddress: _address.Address.fromString(contractId).toScAddress(),
|
|
635
|
+
functionName: functionName,
|
|
636
|
+
args: args
|
|
637
|
+
})),
|
|
638
|
+
subInvocations: []
|
|
639
|
+
})
|
|
640
|
+
});
|
|
641
|
+
var footprint = new _xdr["default"].LedgerFootprint({
|
|
642
|
+
readOnly: [_xdr["default"].LedgerKey.contractData(new _xdr["default"].LedgerKeyContractData({
|
|
643
|
+
contract: _address.Address.fromString(contractId).toScAddress(),
|
|
644
|
+
key: _xdr["default"].ScVal.scvLedgerKeyContractInstance(),
|
|
645
|
+
durability: _xdr["default"].ContractDataDurability.persistent()
|
|
646
|
+
}))],
|
|
647
|
+
readWrite: []
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
// Ledger entries for the destination account
|
|
651
|
+
if (isDestinationContract) {
|
|
652
|
+
footprint.readWrite().push(_xdr["default"].LedgerKey.contractData(new _xdr["default"].LedgerKeyContractData({
|
|
653
|
+
contract: _address.Address.fromString(contractId).toScAddress(),
|
|
654
|
+
key: _xdr["default"].ScVal.scvVec([(0, _scval.nativeToScVal)('Balance', {
|
|
655
|
+
type: 'symbol'
|
|
656
|
+
}), (0, _scval.nativeToScVal)(destination, {
|
|
657
|
+
type: 'address'
|
|
658
|
+
})]),
|
|
659
|
+
durability: _xdr["default"].ContractDataDurability.persistent()
|
|
660
|
+
})));
|
|
661
|
+
if (!isAssetNative) {
|
|
662
|
+
footprint.readOnly().push(_xdr["default"].LedgerKey.account(new _xdr["default"].LedgerKeyAccount({
|
|
663
|
+
accountId: _keypair.Keypair.fromPublicKey(asset.getIssuer()).xdrPublicKey()
|
|
664
|
+
})));
|
|
665
|
+
}
|
|
666
|
+
} else if (isAssetNative) {
|
|
667
|
+
footprint.readWrite().push(_xdr["default"].LedgerKey.account(new _xdr["default"].LedgerKeyAccount({
|
|
668
|
+
accountId: _keypair.Keypair.fromPublicKey(destination).xdrPublicKey()
|
|
669
|
+
})));
|
|
670
|
+
} else if (asset.getIssuer() !== destination) {
|
|
671
|
+
footprint.readWrite().push(_xdr["default"].LedgerKey.trustline(new _xdr["default"].LedgerKeyTrustLine({
|
|
672
|
+
accountId: _keypair.Keypair.fromPublicKey(destination).xdrPublicKey(),
|
|
673
|
+
asset: asset.toTrustLineXDRObject()
|
|
674
|
+
})));
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Ledger entries for the source account
|
|
678
|
+
if (asset.isNative()) {
|
|
679
|
+
footprint.readWrite().push(_xdr["default"].LedgerKey.account(new _xdr["default"].LedgerKeyAccount({
|
|
680
|
+
accountId: _keypair.Keypair.fromPublicKey(source).xdrPublicKey()
|
|
681
|
+
})));
|
|
682
|
+
} else if (asset.getIssuer() !== source) {
|
|
683
|
+
footprint.readWrite().push(_xdr["default"].LedgerKey.trustline(new _xdr["default"].LedgerKeyTrustLine({
|
|
684
|
+
accountId: _keypair.Keypair.fromPublicKey(source).xdrPublicKey(),
|
|
685
|
+
asset: asset.toTrustLineXDRObject()
|
|
686
|
+
})));
|
|
687
|
+
}
|
|
688
|
+
var defaultPaymentFees = {
|
|
689
|
+
instructions: 400000,
|
|
690
|
+
readBytes: 1000,
|
|
691
|
+
writeBytes: 1000,
|
|
692
|
+
resourceFee: BigInt(5000000)
|
|
693
|
+
};
|
|
694
|
+
var sorobanData = new _xdr["default"].SorobanTransactionData({
|
|
695
|
+
resources: new _xdr["default"].SorobanResources({
|
|
696
|
+
footprint: footprint,
|
|
697
|
+
instructions: sorobanFees ? sorobanFees.instructions : defaultPaymentFees.instructions,
|
|
698
|
+
diskReadBytes: sorobanFees ? sorobanFees.readBytes : defaultPaymentFees.readBytes,
|
|
699
|
+
writeBytes: sorobanFees ? sorobanFees.writeBytes : defaultPaymentFees.writeBytes
|
|
700
|
+
}),
|
|
701
|
+
ext: new _xdr["default"].SorobanTransactionDataExt(0),
|
|
702
|
+
resourceFee: new _xdr["default"].Int64(sorobanFees ? sorobanFees.resourceFee : defaultPaymentFees.resourceFee)
|
|
703
|
+
});
|
|
704
|
+
var operation = _operation.Operation.invokeContractFunction({
|
|
705
|
+
contract: contractId,
|
|
706
|
+
"function": functionName,
|
|
707
|
+
args: args,
|
|
708
|
+
auth: [auths]
|
|
709
|
+
});
|
|
710
|
+
this.setSorobanData(sorobanData);
|
|
711
|
+
return this.addOperation(operation);
|
|
712
|
+
}
|
|
713
|
+
|
|
557
714
|
/**
|
|
558
715
|
* This will build the transaction.
|
|
559
716
|
* It will also increment the source account's sequence number by 1.
|
|
@@ -610,6 +767,8 @@ var TransactionBuilder = exports.TransactionBuilder = /*#__PURE__*/function () {
|
|
|
610
767
|
if (this.sorobanData) {
|
|
611
768
|
// @ts-ignore
|
|
612
769
|
attrs.ext = new _xdr["default"].TransactionExt(1, this.sorobanData);
|
|
770
|
+
// Soroban transactions pay the resource fee in addition to the regular fee, so we need to add it here.
|
|
771
|
+
attrs.fee = new _bignumber["default"](attrs.fee).plus(this.sorobanData.resourceFee()).toNumber();
|
|
613
772
|
} else {
|
|
614
773
|
// @ts-ignore
|
|
615
774
|
attrs.ext = new _xdr["default"].TransactionExt(0, _xdr["default"].Void);
|
|
@@ -695,7 +854,6 @@ var TransactionBuilder = exports.TransactionBuilder = /*#__PURE__*/function () {
|
|
|
695
854
|
value: function buildFeeBumpTransaction(feeSource, baseFee, innerTx, networkPassphrase) {
|
|
696
855
|
var innerOps = innerTx.operations.length;
|
|
697
856
|
var minBaseFee = new _bignumber["default"](BASE_FEE);
|
|
698
|
-
var innerInclusionFee = new _bignumber["default"](innerTx.fee).div(innerOps);
|
|
699
857
|
var resourceFee = new _bignumber["default"](0);
|
|
700
858
|
|
|
701
859
|
// Do we need to do special Soroban fee handling? We only want the fee-bump
|
|
@@ -707,12 +865,12 @@ var TransactionBuilder = exports.TransactionBuilder = /*#__PURE__*/function () {
|
|
|
707
865
|
var _sorobanData$resource;
|
|
708
866
|
var sorobanData = env.v1().tx().ext().value();
|
|
709
867
|
resourceFee = new _bignumber["default"]((_sorobanData$resource = sorobanData === null || sorobanData === void 0 ? void 0 : sorobanData.resourceFee()) !== null && _sorobanData$resource !== void 0 ? _sorobanData$resource : 0);
|
|
710
|
-
innerInclusionFee = _bignumber["default"].max(minBaseFee, innerInclusionFee.minus(resourceFee));
|
|
711
868
|
break;
|
|
712
869
|
}
|
|
713
870
|
default:
|
|
714
871
|
break;
|
|
715
872
|
}
|
|
873
|
+
var innerInclusionFee = new _bignumber["default"](innerTx.fee).minus(resourceFee).div(innerOps);
|
|
716
874
|
var base = new _bignumber["default"](baseFee);
|
|
717
875
|
|
|
718
876
|
// The fee rate for fee bump is at least the fee rate of the inner transaction
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stellar/stellar-base",
|
|
3
|
-
"version": "14.0
|
|
3
|
+
"version": "14.1.0",
|
|
4
4
|
"description": "Low-level support library for the Stellar network.",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"browser": {
|
|
@@ -73,10 +73,10 @@
|
|
|
73
73
|
"homepage": "https://github.com/stellar/js-stellar-base",
|
|
74
74
|
"devDependencies": {
|
|
75
75
|
"@babel/cli": "^7.28.3",
|
|
76
|
-
"@babel/core": "^7.28.
|
|
77
|
-
"@babel/eslint-parser": "^7.28.
|
|
76
|
+
"@babel/core": "^7.28.5",
|
|
77
|
+
"@babel/eslint-parser": "^7.28.5",
|
|
78
78
|
"@babel/eslint-plugin": "^7.27.1",
|
|
79
|
-
"@babel/preset-env": "^7.28.
|
|
79
|
+
"@babel/preset-env": "^7.28.5",
|
|
80
80
|
"@babel/register": "^7.28.3",
|
|
81
81
|
"@definitelytyped/dtslint": "^0.0.182",
|
|
82
82
|
"@istanbuljs/nyc-config-babel": "3.0.0",
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
"eslint-webpack-plugin": "^4.2.0",
|
|
98
98
|
"ghooks": "^2.0.4",
|
|
99
99
|
"husky": "^8.0.3",
|
|
100
|
-
"jsdoc": "^4.0.
|
|
100
|
+
"jsdoc": "^4.0.5",
|
|
101
101
|
"karma": "^6.4.4",
|
|
102
102
|
"karma-chrome-launcher": "^3.1.0",
|
|
103
103
|
"karma-coverage": "^2.2.1",
|
|
@@ -110,15 +110,15 @@
|
|
|
110
110
|
"mocha": "^10.8.2",
|
|
111
111
|
"node-polyfill-webpack-plugin": "^3.0.0",
|
|
112
112
|
"nyc": "^15.1.0",
|
|
113
|
-
"prettier": "^3.
|
|
113
|
+
"prettier": "^3.7.4",
|
|
114
114
|
"randombytes": "^2.1.0",
|
|
115
115
|
"sinon": "^16.1.0",
|
|
116
116
|
"sinon-chai": "^3.7.0",
|
|
117
117
|
"taffydb": "^2.7.3",
|
|
118
|
-
"terser-webpack-plugin": "^5.3.
|
|
118
|
+
"terser-webpack-plugin": "^5.3.16",
|
|
119
119
|
"ts-node": "^10.9.2",
|
|
120
120
|
"typescript": "5.6.3",
|
|
121
|
-
"webpack": "^5.
|
|
121
|
+
"webpack": "^5.104.1",
|
|
122
122
|
"webpack-cli": "^5.1.1"
|
|
123
123
|
},
|
|
124
124
|
"dependencies": {
|