@strkfarm/sdk 1.0.45 → 1.0.46
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/dist/index.browser.global.js +141 -98
- package/dist/index.browser.mjs +142 -99
- package/dist/index.d.ts +3 -3
- package/dist/index.js +141 -98
- package/dist/index.mjs +141 -98
- package/package.json +1 -1
- package/src/strategies/ekubo-cl-vault.tsx +165 -114
|
@@ -57,7 +57,7 @@ export interface CLVaultStrategySettings {
|
|
|
57
57
|
newBounds: {
|
|
58
58
|
lower: number;
|
|
59
59
|
upper: number;
|
|
60
|
-
};
|
|
60
|
+
} | string; // if no bounds are set, can say `Managed by Re7`
|
|
61
61
|
// to get true price
|
|
62
62
|
lstContract?: ContractAddr;
|
|
63
63
|
truePrice?: number; // useful for pools where price is known (e.g. USDC/USDT as 1)
|
|
@@ -619,7 +619,7 @@ export class EkuboCLVault extends BaseStrategy<
|
|
|
619
619
|
|
|
620
620
|
static div2Power128(num: BigInt): number {
|
|
621
621
|
return (
|
|
622
|
-
Number((BigInt(num.toString()) *
|
|
622
|
+
Number((BigInt(num.toString()) * BigInt(1e18)) / BigInt(2 ** 128)) / 1e18
|
|
623
623
|
);
|
|
624
624
|
}
|
|
625
625
|
|
|
@@ -647,12 +647,6 @@ export class EkuboCLVault extends BaseStrategy<
|
|
|
647
647
|
tick_spacing: result.pool_key.tick_spacing.toString(),
|
|
648
648
|
extension: result.pool_key.extension.toString(),
|
|
649
649
|
};
|
|
650
|
-
const token0Info = await Global.getTokenInfoFromAddr(poolKey.token0);
|
|
651
|
-
const token1Info = await Global.getTokenInfoFromAddr(poolKey.token1);
|
|
652
|
-
assert(
|
|
653
|
-
token0Info.decimals == token1Info.decimals,
|
|
654
|
-
"Tested only for equal decimals"
|
|
655
|
-
);
|
|
656
650
|
this.poolKey = poolKey;
|
|
657
651
|
return poolKey;
|
|
658
652
|
}
|
|
@@ -661,6 +655,10 @@ export class EkuboCLVault extends BaseStrategy<
|
|
|
661
655
|
const poolKey = await this.getPoolKey();
|
|
662
656
|
const currentPrice = await this._getCurrentPrice(poolKey);
|
|
663
657
|
|
|
658
|
+
if (typeof this.metadata.additionalInfo.newBounds === "string") {
|
|
659
|
+
throw new Error(`New bounds are managed known, to be set manually/externally`);
|
|
660
|
+
}
|
|
661
|
+
|
|
664
662
|
const newLower =
|
|
665
663
|
currentPrice.tick +
|
|
666
664
|
Number(this.metadata.additionalInfo.newBounds.lower) *
|
|
@@ -684,12 +682,12 @@ export class EkuboCLVault extends BaseStrategy<
|
|
|
684
682
|
* @returns {amount0, amount1}
|
|
685
683
|
*/
|
|
686
684
|
private async _getExpectedAmountsForLiquidity(
|
|
687
|
-
|
|
688
|
-
|
|
685
|
+
inputAmount0: Web3Number,
|
|
686
|
+
inputAmount1: Web3Number,
|
|
689
687
|
bounds: EkuboBounds,
|
|
690
688
|
justUseInputAmount = true
|
|
691
689
|
) {
|
|
692
|
-
assert(
|
|
690
|
+
assert(inputAmount0.greaterThan(0) || inputAmount1.greaterThan(0), "Amount is 0");
|
|
693
691
|
|
|
694
692
|
// get amount ratio for 1e18 liquidity
|
|
695
693
|
const sampleLiq = 1e20;
|
|
@@ -713,47 +711,45 @@ export class EkuboCLVault extends BaseStrategy<
|
|
|
713
711
|
);
|
|
714
712
|
// Account for edge cases
|
|
715
713
|
// i.e. when liquidity is out of range
|
|
716
|
-
if (
|
|
714
|
+
if (inputAmount1.eq(0) && inputAmount0.greaterThan(0)) {
|
|
717
715
|
if (sampleAmount1.eq(0)) {
|
|
718
716
|
return {
|
|
719
|
-
amount0:
|
|
720
|
-
amount1: Web3Number.fromWei("0",
|
|
717
|
+
amount0: inputAmount0,
|
|
718
|
+
amount1: Web3Number.fromWei("0", inputAmount1.decimals),
|
|
721
719
|
ratio: Infinity,
|
|
722
720
|
};
|
|
723
721
|
} else if (sampleAmount0.eq(0)) {
|
|
724
722
|
// swap all to token1
|
|
725
723
|
return {
|
|
726
|
-
amount0: Web3Number.fromWei("0",
|
|
727
|
-
|
|
724
|
+
amount0: Web3Number.fromWei("0", inputAmount0.decimals),
|
|
725
|
+
// to ensure decimal consistency, we start with 0
|
|
726
|
+
amount1: Web3Number.fromWei("0", inputAmount1.decimals).plus(inputAmount0.toString()).multipliedBy(price),
|
|
728
727
|
ratio: 0,
|
|
729
728
|
};
|
|
730
729
|
}
|
|
731
|
-
} else if (
|
|
730
|
+
} else if (inputAmount0.eq(0) && inputAmount1.greaterThan(0)) {
|
|
732
731
|
if (sampleAmount0.eq(0)) {
|
|
733
732
|
return {
|
|
734
|
-
amount0: Web3Number.fromWei("0",
|
|
735
|
-
amount1:
|
|
733
|
+
amount0: Web3Number.fromWei("0", inputAmount0.decimals),
|
|
734
|
+
amount1: inputAmount1,
|
|
736
735
|
ratio: 0,
|
|
737
736
|
};
|
|
738
737
|
} else if (sampleAmount1.eq(0)) {
|
|
739
738
|
// swap all to token0
|
|
740
739
|
return {
|
|
741
|
-
|
|
742
|
-
|
|
740
|
+
// to ensure decimal consistency, we start with 0
|
|
741
|
+
amount0: Web3Number.fromWei("0", inputAmount0.decimals).plus(inputAmount1.toString()).dividedBy(price),
|
|
742
|
+
amount1: Web3Number.fromWei("0", inputAmount1.decimals),
|
|
743
743
|
ratio: Infinity,
|
|
744
744
|
};
|
|
745
745
|
}
|
|
746
746
|
}
|
|
747
747
|
|
|
748
|
-
// must make it general later
|
|
749
|
-
assert(
|
|
750
|
-
sampleAmount0.decimals == sampleAmount1.decimals,
|
|
751
|
-
"Sample amounts have different decimals"
|
|
752
|
-
);
|
|
753
748
|
const ratioWeb3Number = sampleAmount0
|
|
754
749
|
.multipliedBy(1e18)
|
|
755
750
|
.dividedBy(sampleAmount1.toString())
|
|
756
751
|
.dividedBy(1e18);
|
|
752
|
+
|
|
757
753
|
const ratio: number = Number(ratioWeb3Number.toFixed(18));
|
|
758
754
|
logger.verbose(
|
|
759
755
|
`${EkuboCLVault.name}: ${
|
|
@@ -763,8 +759,8 @@ export class EkuboCLVault extends BaseStrategy<
|
|
|
763
759
|
|
|
764
760
|
if (justUseInputAmount)
|
|
765
761
|
return this._solveExpectedAmountsEq(
|
|
766
|
-
|
|
767
|
-
|
|
762
|
+
inputAmount0,
|
|
763
|
+
inputAmount1,
|
|
768
764
|
ratioWeb3Number,
|
|
769
765
|
price
|
|
770
766
|
);
|
|
@@ -772,20 +768,20 @@ export class EkuboCLVault extends BaseStrategy<
|
|
|
772
768
|
// we are at liberty to propose amounts outside the propsed amount
|
|
773
769
|
// assuming amount0 and amount1 as independent values, compute other amounts
|
|
774
770
|
// Also, if code came till here, it means both sample amounts are non-zero
|
|
775
|
-
if (
|
|
771
|
+
if (inputAmount1.eq(0) && inputAmount0.greaterThan(0)) {
|
|
776
772
|
// use amount0 as base and compute amount1 using ratio
|
|
777
|
-
const _amount1 =
|
|
773
|
+
const _amount1 = new Web3Number(inputAmount0.toString(), inputAmount1.decimals).dividedBy(ratioWeb3Number);
|
|
778
774
|
return {
|
|
779
|
-
amount0:
|
|
775
|
+
amount0: inputAmount0,
|
|
780
776
|
amount1: _amount1,
|
|
781
777
|
ratio,
|
|
782
778
|
};
|
|
783
|
-
} else if (
|
|
779
|
+
} else if (inputAmount0.eq(0) && inputAmount1.greaterThan(0)) {
|
|
784
780
|
// use amount1 as base and compute amount0 using ratio
|
|
785
|
-
const _amount0 =
|
|
781
|
+
const _amount0 = new Web3Number(inputAmount1.toString(), inputAmount0.decimals).multipliedBy(ratio);
|
|
786
782
|
return {
|
|
787
783
|
amount0: _amount0,
|
|
788
|
-
amount1:
|
|
784
|
+
amount1: inputAmount1,
|
|
789
785
|
ratio,
|
|
790
786
|
};
|
|
791
787
|
} else {
|
|
@@ -876,7 +872,7 @@ export class EkuboCLVault extends BaseStrategy<
|
|
|
876
872
|
};
|
|
877
873
|
}
|
|
878
874
|
|
|
879
|
-
async getSwapInfoToHandleUnused(considerRebalance: boolean = true) {
|
|
875
|
+
async getSwapInfoToHandleUnused(considerRebalance: boolean = true, newBounds: EkuboBounds | null = null): Promise<SwapInfo> {
|
|
880
876
|
const poolKey = await this.getPoolKey();
|
|
881
877
|
|
|
882
878
|
// fetch current unused balances of vault
|
|
@@ -921,7 +917,9 @@ export class EkuboCLVault extends BaseStrategy<
|
|
|
921
917
|
|
|
922
918
|
// get expected amounts for liquidity
|
|
923
919
|
let ekuboBounds: EkuboBounds;
|
|
924
|
-
if (
|
|
920
|
+
if (newBounds) {
|
|
921
|
+
ekuboBounds = newBounds;
|
|
922
|
+
} else if (considerRebalance) {
|
|
925
923
|
ekuboBounds = await this.getNewBounds();
|
|
926
924
|
} else {
|
|
927
925
|
ekuboBounds = await this.getCurrentBounds();
|
|
@@ -1269,7 +1267,11 @@ export class EkuboCLVault extends BaseStrategy<
|
|
|
1269
1267
|
);
|
|
1270
1268
|
}
|
|
1271
1269
|
|
|
1272
|
-
static i129ToNumber(i129: { mag: bigint; sign:
|
|
1270
|
+
static i129ToNumber(i129: { mag: bigint; sign: 0 | 1 | "true" | "false" }): bigint {
|
|
1271
|
+
if (i129.sign == 0 || i129.sign == 1) {
|
|
1272
|
+
return EkuboCLVault.i129ToNumber({mag: i129.mag, sign: i129.sign == 1 ? "true" : "false"});
|
|
1273
|
+
}
|
|
1274
|
+
assert(i129.sign.toString() == 'false' || i129.sign.toString() == 'true', "Invalid sign value");
|
|
1273
1275
|
return i129.mag * (i129.sign.toString() == "false" ? 1n : -1n);
|
|
1274
1276
|
}
|
|
1275
1277
|
|
|
@@ -1483,7 +1485,9 @@ export class EkuboCLVault extends BaseStrategy<
|
|
|
1483
1485
|
subItems: [
|
|
1484
1486
|
{
|
|
1485
1487
|
key: "Range selection",
|
|
1486
|
-
value:
|
|
1488
|
+
value: (typeof this.metadata.additionalInfo.newBounds == 'string') ?
|
|
1489
|
+
this.metadata.additionalInfo.newBounds :
|
|
1490
|
+
`${
|
|
1487
1491
|
this.metadata.additionalInfo.newBounds.lower *
|
|
1488
1492
|
Number(poolKey.tick_spacing)
|
|
1489
1493
|
} to ${
|
|
@@ -1551,86 +1555,89 @@ const faqs: FAQ[] = [
|
|
|
1551
1555
|
),
|
|
1552
1556
|
},
|
|
1553
1557
|
];
|
|
1558
|
+
|
|
1559
|
+
const xSTRKSTRK: IStrategyMetadata<CLVaultStrategySettings> = {
|
|
1560
|
+
name: "Ekubo xSTRK/STRK",
|
|
1561
|
+
description: (
|
|
1562
|
+
<div>
|
|
1563
|
+
<p>{_description.replace("{{POOL_NAME}}", "xSTRK/STRK")}</p>
|
|
1564
|
+
<ul
|
|
1565
|
+
style={{
|
|
1566
|
+
marginLeft: "20px",
|
|
1567
|
+
listStyle: "circle",
|
|
1568
|
+
fontSize: "12px",
|
|
1569
|
+
}}
|
|
1570
|
+
>
|
|
1571
|
+
<li style={{ marginTop: "10px" }}>
|
|
1572
|
+
During withdrawal, you may receive either or both tokens depending
|
|
1573
|
+
on market conditions and prevailing prices.
|
|
1574
|
+
</li>
|
|
1575
|
+
<li style={{ marginTop: "10px" }}>
|
|
1576
|
+
Sometimes you might see a negative APY — this is usually not a big
|
|
1577
|
+
deal. It happens when xSTRK's price drops on DEXes, but things
|
|
1578
|
+
typically bounce back within a few days or a week.
|
|
1579
|
+
</li>
|
|
1580
|
+
</ul>
|
|
1581
|
+
</div>
|
|
1582
|
+
),
|
|
1583
|
+
address: ContractAddr.from(
|
|
1584
|
+
"0x01f083b98674bc21effee29ef443a00c7b9a500fd92cf30341a3da12c73f2324"
|
|
1585
|
+
),
|
|
1586
|
+
launchBlock: 1209881,
|
|
1587
|
+
type: "Other",
|
|
1588
|
+
// must be same order as poolKey token0 and token1
|
|
1589
|
+
depositTokens: [
|
|
1590
|
+
Global.getDefaultTokens().find((t) => t.symbol === "xSTRK")!,
|
|
1591
|
+
Global.getDefaultTokens().find((t) => t.symbol === "STRK")!,
|
|
1592
|
+
],
|
|
1593
|
+
protocols: [_protocol],
|
|
1594
|
+
auditUrl: AUDIT_URL,
|
|
1595
|
+
maxTVL: Web3Number.fromWei("0", 18),
|
|
1596
|
+
risk: {
|
|
1597
|
+
riskFactor: _riskFactor,
|
|
1598
|
+
netRisk:
|
|
1599
|
+
_riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) /
|
|
1600
|
+
_riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
|
|
1601
|
+
notARisks: getNoRiskTags(_riskFactor),
|
|
1602
|
+
},
|
|
1603
|
+
apyMethodology:
|
|
1604
|
+
"APY based on 7-day historical performance, including fees and rewards.",
|
|
1605
|
+
additionalInfo: {
|
|
1606
|
+
newBounds: {
|
|
1607
|
+
lower: -1,
|
|
1608
|
+
upper: 1,
|
|
1609
|
+
},
|
|
1610
|
+
lstContract: ContractAddr.from(
|
|
1611
|
+
"0x028d709c875c0ceac3dce7065bec5328186dc89fe254527084d1689910954b0a"
|
|
1612
|
+
),
|
|
1613
|
+
feeBps: 1000,
|
|
1614
|
+
rebalanceConditions: {
|
|
1615
|
+
customShouldRebalance: async (currentPrice: number) => true,
|
|
1616
|
+
minWaitHours: 24,
|
|
1617
|
+
direction: "uponly",
|
|
1618
|
+
},
|
|
1619
|
+
},
|
|
1620
|
+
faqs: [
|
|
1621
|
+
...faqs,
|
|
1622
|
+
{
|
|
1623
|
+
question: "Why might I see a negative APY?",
|
|
1624
|
+
answer:
|
|
1625
|
+
"A negative APY can occur when xSTRK's price drops on DEXes. This is usually temporary and tends to recover within a few days or a week.",
|
|
1626
|
+
},
|
|
1627
|
+
],
|
|
1628
|
+
points: [{
|
|
1629
|
+
multiplier: 1,
|
|
1630
|
+
logo: 'https://endur.fi/favicon.ico',
|
|
1631
|
+
toolTip: "This strategy holds xSTRK and STRK tokens. Earn 1x Endur points on your xSTRK portion of Liquidity. STRK portion will earn Endur's DEX Bonus points. Points can be found on endur.fi.",
|
|
1632
|
+
}]
|
|
1633
|
+
};
|
|
1634
|
+
|
|
1554
1635
|
/**
|
|
1555
1636
|
* Represents the Vesu Rebalance Strategies.
|
|
1556
1637
|
*/
|
|
1557
1638
|
export const EkuboCLVaultStrategies: IStrategyMetadata<CLVaultStrategySettings>[] =
|
|
1558
1639
|
[
|
|
1559
|
-
|
|
1560
|
-
name: "Ekubo xSTRK/STRK",
|
|
1561
|
-
description: (
|
|
1562
|
-
<div>
|
|
1563
|
-
<p>{_description.replace("{{POOL_NAME}}", "xSTRK/STRK")}</p>
|
|
1564
|
-
<ul
|
|
1565
|
-
style={{
|
|
1566
|
-
marginLeft: "20px",
|
|
1567
|
-
listStyle: "circle",
|
|
1568
|
-
fontSize: "12px",
|
|
1569
|
-
}}
|
|
1570
|
-
>
|
|
1571
|
-
<li style={{ marginTop: "10px" }}>
|
|
1572
|
-
During withdrawal, you may receive either or both tokens depending
|
|
1573
|
-
on market conditions and prevailing prices.
|
|
1574
|
-
</li>
|
|
1575
|
-
<li style={{ marginTop: "10px" }}>
|
|
1576
|
-
Sometimes you might see a negative APY — this is usually not a big
|
|
1577
|
-
deal. It happens when xSTRK's price drops on DEXes, but things
|
|
1578
|
-
typically bounce back within a few days or a week.
|
|
1579
|
-
</li>
|
|
1580
|
-
</ul>
|
|
1581
|
-
</div>
|
|
1582
|
-
),
|
|
1583
|
-
address: ContractAddr.from(
|
|
1584
|
-
"0x01f083b98674bc21effee29ef443a00c7b9a500fd92cf30341a3da12c73f2324"
|
|
1585
|
-
),
|
|
1586
|
-
launchBlock: 1209881,
|
|
1587
|
-
type: "Other",
|
|
1588
|
-
// must be same order as poolKey token0 and token1
|
|
1589
|
-
depositTokens: [
|
|
1590
|
-
Global.getDefaultTokens().find((t) => t.symbol === "xSTRK")!,
|
|
1591
|
-
Global.getDefaultTokens().find((t) => t.symbol === "STRK")!,
|
|
1592
|
-
],
|
|
1593
|
-
protocols: [_protocol],
|
|
1594
|
-
auditUrl: AUDIT_URL,
|
|
1595
|
-
maxTVL: Web3Number.fromWei("0", 18),
|
|
1596
|
-
risk: {
|
|
1597
|
-
riskFactor: _riskFactor,
|
|
1598
|
-
netRisk:
|
|
1599
|
-
_riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) /
|
|
1600
|
-
_riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
|
|
1601
|
-
notARisks: getNoRiskTags(_riskFactor),
|
|
1602
|
-
},
|
|
1603
|
-
apyMethodology:
|
|
1604
|
-
"APY based on 7-day historical performance, including fees and rewards.",
|
|
1605
|
-
additionalInfo: {
|
|
1606
|
-
newBounds: {
|
|
1607
|
-
lower: -1,
|
|
1608
|
-
upper: 1,
|
|
1609
|
-
},
|
|
1610
|
-
lstContract: ContractAddr.from(
|
|
1611
|
-
"0x028d709c875c0ceac3dce7065bec5328186dc89fe254527084d1689910954b0a"
|
|
1612
|
-
),
|
|
1613
|
-
feeBps: 1000,
|
|
1614
|
-
rebalanceConditions: {
|
|
1615
|
-
customShouldRebalance: async (currentPrice: number) => true,
|
|
1616
|
-
minWaitHours: 24,
|
|
1617
|
-
direction: "uponly",
|
|
1618
|
-
},
|
|
1619
|
-
},
|
|
1620
|
-
faqs: [
|
|
1621
|
-
...faqs,
|
|
1622
|
-
{
|
|
1623
|
-
question: "Why might I see a negative APY?",
|
|
1624
|
-
answer:
|
|
1625
|
-
"A negative APY can occur when xSTRK's price drops on DEXes. This is usually temporary and tends to recover within a few days or a week.",
|
|
1626
|
-
},
|
|
1627
|
-
],
|
|
1628
|
-
points: [{
|
|
1629
|
-
multiplier: 1,
|
|
1630
|
-
logo: 'https://endur.fi/favicon.ico',
|
|
1631
|
-
toolTip: "This strategy holds xSTRK and STRK tokens. Earn 1x Endur points on your xSTRK portion of Liquidity. STRK portion will earn Endur's DEX Bonus points. Points can be found on endur.fi.",
|
|
1632
|
-
}]
|
|
1633
|
-
},
|
|
1640
|
+
xSTRKSTRK,
|
|
1634
1641
|
{
|
|
1635
1642
|
name: "Ekubo USDC/USDT",
|
|
1636
1643
|
description: (
|
|
@@ -1690,4 +1697,48 @@ export const EkuboCLVaultStrategies: IStrategyMetadata<CLVaultStrategySettings>[
|
|
|
1690
1697
|
},
|
|
1691
1698
|
faqs: [...faqs],
|
|
1692
1699
|
},
|
|
1693
|
-
|
|
1700
|
+
// {
|
|
1701
|
+
// ...xSTRKSTRK,
|
|
1702
|
+
// name: "Ekubo STRK/USDC",
|
|
1703
|
+
// description: (
|
|
1704
|
+
// <div>
|
|
1705
|
+
// <p>{_description.replace("{{POOL_NAME}}", "STRK/USDC")}</p>
|
|
1706
|
+
// <ul
|
|
1707
|
+
// style={{
|
|
1708
|
+
// marginLeft: "20px",
|
|
1709
|
+
// listStyle: "circle",
|
|
1710
|
+
// fontSize: "12px",
|
|
1711
|
+
// }}
|
|
1712
|
+
// >
|
|
1713
|
+
// <li style={{ marginTop: "10px" }}>
|
|
1714
|
+
// During withdrawal, you may receive either or both tokens depending
|
|
1715
|
+
// on market conditions and prevailing prices.
|
|
1716
|
+
// </li>
|
|
1717
|
+
// </ul>
|
|
1718
|
+
// </div>
|
|
1719
|
+
// ),
|
|
1720
|
+
// address: ContractAddr.from(
|
|
1721
|
+
// "0xb7bd37121041261446d8eedec618955a4490641034942da688e8cbddea7b23"
|
|
1722
|
+
// ),
|
|
1723
|
+
// launchBlock: 1492136,
|
|
1724
|
+
// // must be same order as poolKey token0 and token1
|
|
1725
|
+
// depositTokens: [
|
|
1726
|
+
// Global.getDefaultTokens().find((t) => t.symbol === "STRK")!,
|
|
1727
|
+
// Global.getDefaultTokens().find((t) => t.symbol === "USDC")!,
|
|
1728
|
+
// ],
|
|
1729
|
+
// maxTVL: Web3Number.fromWei("0", 6),
|
|
1730
|
+
// additionalInfo: {
|
|
1731
|
+
// newBounds: "Managed by Re7",
|
|
1732
|
+
// feeBps: 1000,
|
|
1733
|
+
// rebalanceConditions: {
|
|
1734
|
+
// customShouldRebalance: async (currentPrice: number) =>
|
|
1735
|
+
// true,
|
|
1736
|
+
// minWaitHours: 6,
|
|
1737
|
+
// direction: "any",
|
|
1738
|
+
// },
|
|
1739
|
+
// },
|
|
1740
|
+
// },
|
|
1741
|
+
];
|
|
1742
|
+
|
|
1743
|
+
|
|
1744
|
+
// 0x65b6a3ae00e7343ca8b2463d81401716c6581c18336206f31085c06a7d63936
|