diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 2eb06aaa2..b5d987ce7 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -36,9 +36,8 @@ import { WalletTutorialPageComponent } from "./tutorial/wallet-tutorial-page/wal import { SellCreatorCoinsTutorialComponent } from "./tutorial/sell-creator-coins-tutorial-page/sell-creator-coins-tutorial/sell-creator-coins-tutorial.component"; import { DiamondTutorialPageComponent } from "./tutorial/diamond-tutorial-page/diamond-tutorial-page.component"; import { CreatePostTutorialPageComponent } from "./tutorial/create-post-tutorial-page/create-post-tutorial-page.component"; -import { - SupplyMonitoringStatsPageComponent -} from "./supply-monitoring-stats-page/supply-monitoring-stats-page.component"; +import { SupplyMonitoringStatsPageComponent } from "./supply-monitoring-stats-page/supply-monitoring-stats-page.component"; +import { DaoCoinsPageComponent } from "./dao-coins/dao-coins-page/dao-coins-page.component"; class RouteNames { // Not sure if we should have a smarter schema for this, e.g. what happens if we have @@ -85,6 +84,7 @@ class RouteNames { public static CREATE_PROFILE = "create-profile"; public static INVEST = "invest"; public static SUPPLY_STATS = "supply-stats"; + public static DAO = "dao"; } const routes: Routes = [ @@ -108,6 +108,7 @@ const routes: Routes = [ { path: RouteNames.POSTS + "/:postHashHex", component: PostThreadPageComponent, pathMatch: "full" }, { path: RouteNames.NFT + "/:postHashHex", component: NftPostPageComponent, pathMatch: "full" }, { path: RouteNames.SEND_DESO, component: TransferDeSoPageComponent, pathMatch: "full" }, + { path: RouteNames.DAO, component: DaoCoinsPageComponent, pathMatch: "full" }, { path: RouteNames.TOS, component: TosPageComponent, pathMatch: "full" }, { path: "tos", component: TosPageComponent, pathMatch: "full" }, { path: RouteNames.ADMIN, component: AdminPageComponent, pathMatch: "full" }, diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 31a3c9883..7d47978dc 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -163,6 +163,10 @@ import { TransferNftModalComponent } from "./transfer-nft-modal/transfer-nft-mod import { TransferNftAcceptModalComponent } from "./transfer-nft-accept-modal/transfer-nft-accept-modal.component"; import { NftBurnModalComponent } from "./nft-burn-modal/nft-burn-modal.component"; import { NftSelectSerialNumberComponent } from "./nft-select-serial-number/nft-select-serial-number.component"; +import { DaoCoinsComponent } from "./dao-coins/dao-coins.component"; +import { DaoCoinsPageComponent } from "./dao-coins/dao-coins-page/dao-coins-page.component"; +import { TransferDAOCoinModalComponent } from "./dao-coins/transfer-dao-coin-modal/transfer-dao-coin-modal.component"; +import { BurnDaoCoinModalComponent } from "./dao-coins/burn-dao-coin-modal/burn-dao-coin-modal.component"; // Modular Themes for DeSo by Carsen Klock @carsenk import { ThemeModule } from "./theme/theme.module"; @@ -315,6 +319,10 @@ const greenishTheme: Theme = { key: "greenish", name: "Green Theme" }; TransferNftModalComponent, NftBurnModalComponent, NftSelectSerialNumberComponent, + DaoCoinsComponent, + DaoCoinsPageComponent, + TransferDAOCoinModalComponent, + BurnDaoCoinModalComponent, ], imports: [ BrowserModule, diff --git a/src/app/backend-api.service.ts b/src/app/backend-api.service.ts index 0aa0ad243..d467b52d5 100644 --- a/src/app/backend-api.service.ts +++ b/src/app/backend-api.service.ts @@ -8,6 +8,7 @@ import { map, switchMap, catchError, filter, take, concatMap } from "rxjs/operat import { HttpClient, HttpErrorResponse } from "@angular/common/http"; import { IdentityService } from "./identity.service"; import { environment } from "src/environments/environment"; +import { Hex } from "web3-utils/types"; export class BackendRoutes { static ExchangeRateRoute = "/api/v0/get-exchange-rate"; @@ -27,6 +28,7 @@ export class BackendRoutes { static RoutePathGetPostsForPublicKey = "/api/v0/get-posts-for-public-key"; static RoutePathGetDiamondedPosts = "/api/v0/get-diamonded-posts"; static RoutePathGetHodlersForPublicKey = "/api/v0/get-hodlers-for-public-key"; + static RoutePathIsHodlingPublicKey = "/api/v0/is-hodling-public-key"; static RoutePathSendMessageStateless = "/api/v0/send-message-stateless"; static RoutePathGetMessagesStateless = "/api/v0/get-messages-stateless"; static RoutePathMarkContactMessagesRead = "/api/v0/mark-contact-messages-read"; @@ -90,6 +92,10 @@ export class BackendRoutes { static RoutePathAcceptNFTTransfer = "/api/v0/accept-nft-transfer"; static RoutePathBurnNFT = "/api/v0/burn-nft"; + // DAO routes + static RoutePathDAOCoin = "/api/v0/dao-coin"; + static RoutePathTransferDAOCoin = "/api/v0/transfer-dao-coin"; + // ETH static RoutePathSubmitETHTx = "/api/v0/submit-eth-tx"; static RoutePathQueryETHRPC = "/api/v0/query-eth-rpc"; @@ -177,6 +183,13 @@ export class Transaction { signatureBytesHex: string; } +export type DAOCoinEntryResponse = { + CoinsInCirculationNanos: Hex; + MintingDisabled: boolean; + NumberOfHolders: number; + TransferRestrictionStatus: TransferRestrictionStatusString; +}; + export class ProfileEntryResponse { Username: string; Description: string; @@ -187,6 +200,7 @@ export class ProfileEntryResponse { CoinsInCirculationNanos: number; CreatorBasisPoints: number; }; + DAOCoinEntry?: DAOCoinEntryResponse; CoinPriceDeSoNanos?: number; StakeMultipleBasisPoints?: number; PublicKeyBase58Check?: string; @@ -328,6 +342,8 @@ export class BalanceEntryResponse { HasPurchased: boolean; // How much this HODLer owns of a particular creator coin. BalanceNanos: number; + // Use this balance for DAO Coin balances + BalanceNanosUint256: Hex; // The net effect of transactions in the mempool on a given BalanceEntry's BalanceNanos. // This is used by the frontend to convey info about mining. NetBalanceInMempool: number; @@ -442,6 +458,20 @@ export type CountryLevelSignUpBonusResponse = { CountryCodeDetails: CountryCodeDetails; }; +export enum DAOCoinOperationTypeString { + MINT = "mint", + BURN = "burn", + UPDATE_TRANSFER_RESTRICTION_STATUS = "update_transfer_restriction_status", + DISABLE_MINTING = "disable_minting", +} + +export enum TransferRestrictionStatusString { + UNRESTRICTED = "unrestricted", + PROFILE_OWNER_ONLY = "profile_owner_only", + DAO_MEMBERS_ONLY = "dao_members_only", + PERMANENTLY_UNRESTRICTED = "permanently_unrestricted", +} + @Injectable({ providedIn: "root", }) @@ -1340,8 +1370,9 @@ export class BackendApiService { LastPublicKeyBase58Check: string, NumToFetch: number, FetchHodlings: boolean = false, - FetchAll: boolean = false - ): Observable { + FetchAll: boolean = false, + IsDAOCoin: boolean = false + ): Observable<{ Hodlers: BalanceEntryResponse[]; LastPublicKeyBase58Check: string }> { return this.post(endpoint, BackendRoutes.RoutePathGetHodlersForPublicKey, { PublicKeyBase58Check, Username, @@ -1349,8 +1380,23 @@ export class BackendApiService { NumToFetch, FetchHodlings, FetchAll, + IsDAOCoin, + }); + } + + IsHodlingPublicKey( + endpoint: string, + PublicKeyBase58Check: string, + IsHodlingPublicKeyBase58Check: string, + IsDAOCoin: boolean + ): Observable<{ IsHodling: boolean; BalanceEntry: BalanceEntryResponse }> { + return this.post(endpoint, BackendRoutes.RoutePathIsHodlingPublicKey, { + PublicKeyBase58Check, + IsHodlingPublicKeyBase58Check, + IsDAOCoin, }); } + UpdateProfile( endpoint: string, // Specific fields @@ -1690,6 +1736,46 @@ export class BackendApiService { return request; } + DAOCoin( + endpoint: string, + UpdaterPublicKeyBase58Check: string, + ProfilePublicKeyBase58CheckOrUsername: string, + OperationType: DAOCoinOperationTypeString, + TransferRestrictionStatus: TransferRestrictionStatusString | undefined, + CoinsToMintNanos: Hex | undefined, + CoinsToBurnNanos: Hex | undefined, + MinFeeRateNanosPerKB: number + ): Observable { + const request = this.post(endpoint, BackendRoutes.RoutePathDAOCoin, { + UpdaterPublicKeyBase58Check, + ProfilePublicKeyBase58CheckOrUsername, + OperationType, + CoinsToMintNanos, + CoinsToBurnNanos, + TransferRestrictionStatus, + MinFeeRateNanosPerKB, + }); + return this.signAndSubmitTransaction(endpoint, request, UpdaterPublicKeyBase58Check); + } + + TransferDAOCoin( + endpoint: string, + SenderPublicKeyBase58Check: string, + ProfilePublicKeyBase58CheckOrUsername: string, + ReceiverPublicKeyBase58CheckOrUsername: string, + DAOCoinToTransferNanos: Hex, + MinFeeRateNanosPerKB: number + ): Observable { + const request = this.post(endpoint, BackendRoutes.RoutePathTransferDAOCoin, { + SenderPublicKeyBase58Check, + ProfilePublicKeyBase58CheckOrUsername, + ReceiverPublicKeyBase58CheckOrUsername, + DAOCoinToTransferNanos, + MinFeeRateNanosPerKB, + }); + return this.signAndSubmitTransaction(endpoint, request, SenderPublicKeyBase58Check); + } + BlockPublicKey( endpoint: string, PublicKeyBase58Check: string, diff --git a/src/app/creator-profile-page/creator-profile-details/creator-profile-details.component.html b/src/app/creator-profile-page/creator-profile-details/creator-profile-details.component.html index 77386a131..0a6aeed33 100644 --- a/src/app/creator-profile-page/creator-profile-details/creator-profile-details.component.html +++ b/src/app/creator-profile-page/creator-profile-details/creator-profile-details.component.html @@ -14,7 +14,7 @@ @@ -83,7 +83,7 @@
- Holders of ${{ profile.Username }} coin + Holders of ${{ profile.Username }}'s Creator Coin
@@ -109,6 +109,26 @@
+ +
+
+
+ Holders of ${{ profile.Username }}'s DAO Coin +
+
+ +
+
+
+
Username or PubKey
+
Coins Held
+
+ +
+
+
+
+
diff --git a/src/app/creator-profile-page/creator-profile-details/creator-profile-details.component.ts b/src/app/creator-profile-page/creator-profile-details/creator-profile-details.component.ts index 1e9065b0f..e620d1d9a 100644 --- a/src/app/creator-profile-page/creator-profile-details/creator-profile-details.component.ts +++ b/src/app/creator-profile-page/creator-profile-details/creator-profile-details.component.ts @@ -21,6 +21,7 @@ export class CreatorProfileDetailsComponent implements OnInit { // Leaving this one in so old links will direct to the Coin Purchasers tab. "creator-coin": "Creator Coin", "coin-purchasers": "Creator Coin", + dao: "DAO Coin", diamonds: "Diamonds", nfts: "NFTs", }; @@ -29,6 +30,7 @@ export class CreatorProfileDetailsComponent implements OnInit { "Creator Coin": "creator-coin", Diamonds: "diamonds", NFTs: "nfts", + "DAO Coin": "dao", }; appData: GlobalVarsService; userName: string; diff --git a/src/app/creator-profile-page/creator-profile-hodlers/creator-profile-hodlers.component.html b/src/app/creator-profile-page/creator-profile-hodlers/creator-profile-hodlers.component.html index ecc3b19d3..9056de1e0 100644 --- a/src/app/creator-profile-page/creator-profile-hodlers/creator-profile-hodlers.component.html +++ b/src/app/creator-profile-page/creator-profile-hodlers/creator-profile-hodlers.component.html @@ -1,7 +1,12 @@
- No one owns ${{ profile.Username }} coin yet.  - Be the first! + No one owns ${{ profile.Username }}'s {{ isDAOCoin ? "DAO" : "Creator" }} Coin yet.  + + Be the first! +
@@ -23,6 +28,7 @@ matTooltipClass="global__mat-tooltip global__mat-tooltip-font-size" [matTooltip]="getTooltipForRow(row)" #tooltip="matTooltip" + *ngIf="!isDAOCoin" >
@@ -80,6 +86,7 @@ matTooltipClass="global__mat-tooltip global__mat-tooltip-font-size" [matTooltip]="getTooltipForRow(row)" #tooltip="matTooltip" + *ngIf="!isDAOCoin" >
@@ -99,20 +106,23 @@
-
- {{ (row.BalanceNanos / 1e9).toFixed(4) }} +
+ {{ isDAOCoin ? globalVars.hexNanosToUnitString(row.BalanceNanosUint256) : (row.BalanceNanos / 1e9).toFixed(4) }}
-
+
≈ {{ globalVars.creatorCoinNanosToUSDNaive(row.BalanceNanos, profile.CoinPriceDeSoNanos, true) }}
Total
-
- {{ (profile.CoinEntry.CoinsInCirculationNanos / 1e9).toFixed(4) }} +
+ {{ isDAOCoin ? + globalVars.hexNanosToUnitString(profile.DAOCoinEntry.CoinsInCirculationNanos) : + (profile.CoinEntry.CoinsInCirculationNanos / 1e9).toFixed(4) + }}
-
+
≈ {{ globalVars.creatorCoinNanosToUSDNaive( diff --git a/src/app/creator-profile-page/creator-profile-hodlers/creator-profile-hodlers.component.ts b/src/app/creator-profile-page/creator-profile-hodlers/creator-profile-hodlers.component.ts index dc7ab3353..18246a184 100644 --- a/src/app/creator-profile-page/creator-profile-hodlers/creator-profile-hodlers.component.ts +++ b/src/app/creator-profile-page/creator-profile-hodlers/creator-profile-hodlers.component.ts @@ -17,6 +17,7 @@ export class CreatorProfileHodlersComponent { constructor(private globalVars: GlobalVarsService, private backendApi: BackendApiService) {} @Input() profile: ProfileEntryResponse; + @Input() isDAOCoin: boolean = false; showTotal = false; lastPage = null; @@ -40,7 +41,8 @@ export class CreatorProfileHodlersComponent { lastPublicKeyBase58Check, CreatorProfileHodlersComponent.PAGE_SIZE, false, - false + false, + this.isDAOCoin, ) .toPromise() .then((res) => { diff --git a/src/app/dao-coins/burn-dao-coin-modal/burn-dao-coin-modal.component.html b/src/app/dao-coins/burn-dao-coin-modal/burn-dao-coin-modal.component.html new file mode 100644 index 000000000..b95b67203 --- /dev/null +++ b/src/app/dao-coins/burn-dao-coin-modal/burn-dao-coin-modal.component.html @@ -0,0 +1,35 @@ +
+
+ Burn {{ balanceEntryResponse.ProfileEntryResponse?.Username }} DAO Coins +
+
+ Your Balance: {{ globalVars.hexNanosToUnitString(balanceEntryResponse.BalanceNanosUint256) }} {{ balanceEntryResponse.ProfileEntryResponse?.Username }} DAO Coins +
+
+
+ Amount To Burn +
+ +
+ + +
+ {{ validationError }} +
+
+
+ {{ backendErrors }} +
+
diff --git a/src/app/dao-coins/burn-dao-coin-modal/burn-dao-coin-modal.component.ts b/src/app/dao-coins/burn-dao-coin-modal/burn-dao-coin-modal.component.ts new file mode 100644 index 000000000..b9a29639b --- /dev/null +++ b/src/app/dao-coins/burn-dao-coin-modal/burn-dao-coin-modal.component.ts @@ -0,0 +1,62 @@ +import { Component, Input } from "@angular/core"; +import { BsModalRef, BsModalService } from "ngx-bootstrap/modal"; +import { GlobalVarsService } from "../../global-vars.service"; +import { BackendApiService, BalanceEntryResponse, DAOCoinOperationTypeString } from "../../backend-api.service"; +import { toBN } from "web3-utils"; + +@Component({ + selector: "burn-dao-coin-modal", + templateUrl: "./burn-dao-coin-modal.component.html", +}) +export class BurnDaoCoinModalComponent { + @Input() balanceEntryResponse: BalanceEntryResponse; + + amountToBurn: number = 0; + burningDAOCoin: boolean = false; + validationErrors: string[] = []; + backendErrors: string = ""; + constructor( + public bsModalRef: BsModalRef, + public modalService: BsModalService, + public globalVars: GlobalVarsService, + private backendApi: BackendApiService + ) {} + + burnDAOCoin(): void { + this.burningDAOCoin = true; + this.backendErrors = ""; + this.backendApi + .DAOCoin( + this.globalVars.localNode, + this.globalVars.loggedInUser?.PublicKeyBase58Check, + this.balanceEntryResponse.CreatorPublicKeyBase58Check, + DAOCoinOperationTypeString.BURN, + undefined, + undefined, + this.globalVars.toHexNanos(this.amountToBurn), + this.globalVars.defaultFeeRateNanosPerKB + ) + .subscribe( + (res) => { + this.modalService.setDismissReason(`dao coins burned|${this.globalVars.toHexNanos(this.amountToBurn)}`); + this.bsModalRef.hide(); + }, + (err) => { + this.backendErrors = err.error.error; + console.error(err); + } + ) + .add(() => (this.burningDAOCoin = false)); + } + + updateValidationErrors(): void { + let err: string[] = []; + if (this.amountToBurn <= 0) { + err.push("Must transfer a non-zero amount\n"); + } + if (this.globalVars.unitToBNNanos(this.amountToBurn || 0).gt(toBN(this.balanceEntryResponse.BalanceNanosUint256))) { + err.push("Amount to burn exceeds balance\n"); + } + this.validationErrors = err; + } +} diff --git a/src/app/dao-coins/dao-coins-page/dao-coins-page.component.html b/src/app/dao-coins/dao-coins-page/dao-coins-page.component.html new file mode 100644 index 000000000..4a41fca29 --- /dev/null +++ b/src/app/dao-coins/dao-coins-page/dao-coins-page.component.html @@ -0,0 +1,3 @@ + + + diff --git a/src/app/dao-coins/dao-coins-page/dao-coins-page.component.scss b/src/app/dao-coins/dao-coins-page/dao-coins-page.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/dao-coins/dao-coins-page/dao-coins-page.component.spec.ts b/src/app/dao-coins/dao-coins-page/dao-coins-page.component.spec.ts new file mode 100644 index 000000000..8a6e94c61 --- /dev/null +++ b/src/app/dao-coins/dao-coins-page/dao-coins-page.component.spec.ts @@ -0,0 +1,24 @@ +import { async, ComponentFixture, TestBed } from "@angular/core/testing"; + +import { DaoCoinsPageComponent } from "./wallet-page.component"; + +describe("WalletPageComponent", () => { + let component: DaoCoinsPageComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [DaoCoinsPageComponent], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DaoCoinsPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it("should create", () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/dao-coins/dao-coins-page/dao-coins-page.component.ts b/src/app/dao-coins/dao-coins-page/dao-coins-page.component.ts new file mode 100644 index 000000000..90e286ee1 --- /dev/null +++ b/src/app/dao-coins/dao-coins-page/dao-coins-page.component.ts @@ -0,0 +1,11 @@ +import { Component } from "@angular/core"; +import { GlobalVarsService } from "../../global-vars.service"; + +@Component({ + selector: "dao-coins-page", + templateUrl: "./dao-coins-page.component.html", + styleUrls: ["./dao-coins-page.component.scss"], +}) +export class DaoCoinsPageComponent { + constructor(public globalVars: GlobalVarsService) {} +} diff --git a/src/app/dao-coins/dao-coins.component.html b/src/app/dao-coins/dao-coins.component.html new file mode 100644 index 000000000..721bab230 --- /dev/null +++ b/src/app/dao-coins/dao-coins.component.html @@ -0,0 +1,263 @@ + +
+ +
+
DAO Coins
+
+
+ +
+ + + + +
+
My DAO
+
+
+ Coins In Circulation:  + {{ globalVars.hexNanosToUnitString(myDAOCoin?.CoinsInCirculationNanos || 0) }} +
+
+ Transfer Restriction Status:  + {{ getDisplayTransferRestrictionStatus(myDAOCoin?.TransferRestrictionStatus) }} +
+
+ Minting Disabled:  + {{ myDAOCoin?.MintingDisabled || false }} +
+
+
+ + + +
+ +
+
+ + +
+
+ +
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + +
+
+ +
+
+
+
+ + + +
+
DAO Coins
+
+ + + + +
+ + +
+
+
+ {{ emptyHodlerListMessage() }} +
+ +
+ +
+
+ +
+ +
+
+ + + +
diff --git a/src/app/dao-coins/dao-coins.component.ts b/src/app/dao-coins/dao-coins.component.ts new file mode 100644 index 000000000..3edece213 --- /dev/null +++ b/src/app/dao-coins/dao-coins.component.ts @@ -0,0 +1,521 @@ +import { Component, Input, OnDestroy, OnInit } from "@angular/core"; +import { GlobalVarsService } from "../global-vars.service"; +import { AppRoutingModule } from "../app-routing.module"; +import { + BackendApiService, + BalanceEntryResponse, + DAOCoinEntryResponse, + DAOCoinOperationTypeString, + TransferRestrictionStatusString, +} from "../backend-api.service"; +import { Title } from "@angular/platform-browser"; +import { ActivatedRoute, Router } from "@angular/router"; +import { InfiniteScroller } from "../infinite-scroller"; +import { IAdapter, IDatasource } from "ngx-ui-scroll"; +import { Observable, Subscription, throwError, zip } from "rxjs"; +import { environment } from "src/environments/environment"; +import { toBN } from "web3-utils"; +import { catchError, map } from "rxjs/operators"; +import { BsModalService } from "ngx-bootstrap/modal"; +import { TransferDAOCoinModalComponent } from "./transfer-dao-coin-modal/transfer-dao-coin-modal.component"; +import { BurnDaoCoinModalComponent } from "./burn-dao-coin-modal/burn-dao-coin-modal.component"; +import { SwalHelper } from "../../lib/helpers/swal-helper"; + +@Component({ + selector: "dao-coins", + templateUrl: "./dao-coins.component.html", +}) +export class DaoCoinsComponent implements OnInit, OnDestroy { + static PAGE_SIZE = 20; + static BUFFER_SIZE = 10; + static WINDOW_VIEWPORT = true; + static PADDING = 0.5; + + @Input() inTutorial: boolean; + + globalVars: GlobalVarsService; + AppRoutingModule = AppRoutingModule; + hasUnminedCreatorCoins: boolean; + + sortedCoinsFromHighToLow: number = 0; + sortedUsernameFromHighToLow: number = 0; + hideMyDAOTab: boolean = false; + showDAOCoinHoldings: boolean = false; + + myDAOCoin: DAOCoinEntryResponse; + myDAOCapTable: BalanceEntryResponse[] = []; + daoCoinHoldings: BalanceEntryResponse[] = []; + + loadingMyDAOCapTable: boolean = false; + loadingMyDAOCoinHoldings: boolean = false; + loadingNewSelection: boolean = false; + + static myDAOTab: string = "My DAO"; + static daoCoinsTab: string = "DAO Holdings"; + tabs = [DaoCoinsComponent.myDAOTab, DaoCoinsComponent.daoCoinsTab]; + activeTab: string = DaoCoinsComponent.myDAOTab; + balanceEntryToHihlight: BalanceEntryResponse; + + TransferRestrictionStatusString = TransferRestrictionStatusString; + transferRestrictionStatus: TransferRestrictionStatusString; + coinsToMint: number = 0; + coinsToBurn: number = 0; + mintingDAOCoin: boolean = false; + disablingMinting: boolean = false; + burningDAOCoin: boolean = false; + updatingTransferRestrictionStatus: boolean = false; + + transferRestrictionStatusOptions = [ + TransferRestrictionStatusString.UNRESTRICTED, + TransferRestrictionStatusString.PROFILE_OWNER_ONLY, + TransferRestrictionStatusString.DAO_MEMBERS_ONLY, + TransferRestrictionStatusString.PERMANENTLY_UNRESTRICTED, + ]; + + constructor( + private appData: GlobalVarsService, + private titleService: Title, + private router: Router, + private route: ActivatedRoute, + public backendApi: BackendApiService, + private modalService: BsModalService + ) { + this.globalVars = appData; + } + + subscriptions = new Subscription(); + + ngOnInit() { + // Don't look up my DAO if I don't have a profile + if (this.globalVars.loggedInUser?.ProfileEntryResponse) { + this.myDAOCoin = this.globalVars.loggedInUser.ProfileEntryResponse.DAOCoinEntry; + this.transferRestrictionStatus = + this.myDAOCoin?.TransferRestrictionStatus || TransferRestrictionStatusString.UNRESTRICTED; + this.loadMyDAOCapTable().subscribe((res) => {}); + } else { + this.hideMyDAOTab = true; + this.showDAOCoinHoldings = true; + this.activeTab = DaoCoinsComponent.daoCoinsTab; + this.tabs = [DaoCoinsComponent.daoCoinsTab]; + } + this.loadMyDAOCoinHoldings().subscribe((res) => {}); + this.titleService.setTitle(`DAO Coins - ${environment.node.name}`); + } + + ngOnDestroy(): void { + this.subscriptions.unsubscribe(); + } + + loadMyDAOCapTable(): Observable { + this.loadingMyDAOCapTable = true; + return this.backendApi + .GetHodlersForPublicKey( + this.globalVars.localNode, + this.globalVars.loggedInUser?.PublicKeyBase58Check, + "", + "", + 0, + false, + true, + true + ) + .pipe( + map((res) => { + this.myDAOCapTable = res.Hodlers || []; + this.loadingMyDAOCapTable = false; + return res.Hodlers; + }), + catchError((err) => { + console.error(err); + this.loadingMyDAOCapTable = false; + return throwError(err); + }) + ); + } + + loadMyDAOCoinHoldings(): Observable { + this.loadingMyDAOCoinHoldings = true; + return this.backendApi + .GetHodlersForPublicKey( + this.globalVars.localNode, + this.globalVars.loggedInUser?.PublicKeyBase58Check, + "", + "", + 0, + true, + true, + true + ) + .pipe( + map((res) => { + this.daoCoinHoldings = res.Hodlers || []; + this.loadingMyDAOCoinHoldings = false; + this.loadingMyDAOCoinHoldings = false; + return res.Hodlers; + }), + catchError((err) => { + console.error(err); + this.loadingMyDAOCoinHoldings = false; + return throwError(err); + }) + ); + } + + // Thanks to @brabenetz for the solution on forward padding with the ngx-ui-scroll component. + // https://github.com/dhilt/ngx-ui-scroll/issues/111#issuecomment-697269318 + correctDataPaddingForwardElementHeight(viewportElement: HTMLElement): void { + const dataPaddingForwardElement: HTMLElement = viewportElement.querySelector(`[data-padding-forward]`); + if (dataPaddingForwardElement) { + dataPaddingForwardElement.setAttribute("style", "height: 0px;"); + } + } + + // sort by Coins held + sortHodlingsCoins(hodlings: BalanceEntryResponse[], descending: boolean): void { + this.sortedUsernameFromHighToLow = 0; + this.sortedCoinsFromHighToLow = descending ? -1 : 1; + hodlings.sort((a: BalanceEntryResponse, b: BalanceEntryResponse) => { + return this.sortedCoinsFromHighToLow * (a.BalanceNanos - b.BalanceNanos); + }); + } + + // sort by username + sortHodlingsUsername(hodlings: BalanceEntryResponse[], descending: boolean): void { + this.sortedUsernameFromHighToLow = descending ? -1 : 1; + this.sortedCoinsFromHighToLow = 0; + hodlings.sort((a: BalanceEntryResponse, b: BalanceEntryResponse) => { + return ( + this.sortedUsernameFromHighToLow * + b.ProfileEntryResponse.Username.localeCompare(a.ProfileEntryResponse.Username) + ); + }); + } + + sortWallet(column: string) { + let descending: boolean; + switch (column) { + case "username": + // code block + descending = this.sortedUsernameFromHighToLow !== -1; + this.sortHodlingsUsername(this.myDAOCapTable, descending); + this.sortHodlingsUsername(this.daoCoinHoldings, descending); + break; + case "coins": + descending = this.sortedCoinsFromHighToLow !== -1; + this.sortHodlingsCoins(this.myDAOCapTable, descending); + this.sortHodlingsCoins(this.daoCoinHoldings, descending); + break; + default: + // do nothing + } + this.scrollerReset(); + } + + mintDAOCoin(): void { + if (this.myDAOCoin.MintingDisabled || this.mintingDAOCoin || this.coinsToMint <= 0) { + return; + } + SwalHelper.fire({ + target: this.globalVars.getTargetComponentSelector(), + title: "Mint DAO Coins", + html: `Click confirm to mint ${this.coinsToMint} ${this.globalVars.loggedInUser?.ProfileEntryResponse?.Username} DAO coins`, + showCancelButton: true, + customClass: { + confirmButton: "btn btn-light", + cancelButton: "btn btn-light no", + }, + reverseButtons: true, + }).then((res: any) => { + if (res.isConfirmed) { + this.loadingNewSelection = true; + this.mintingDAOCoin = true; + this.doDAOCoinTxn(this.globalVars.loggedInUser?.PublicKeyBase58Check, DAOCoinOperationTypeString.MINT) + .subscribe( + (res) => { + this.myDAOCoin.CoinsInCirculationNanos = toBN(this.myDAOCoin.CoinsInCirculationNanos) + .add(toBN(this.globalVars.toHexNanos(this.coinsToMint))) + .toString("hex"); + zip(this.loadMyDAOCapTable(), this.loadMyDAOCoinHoldings()).subscribe(() => { + this.loadingNewSelection = false; + this._handleTabClick(this.activeTab); + }); + this.coinsToMint = 0; + }, + (err) => { + this.globalVars._alertError(err.error.error); + console.error(err); + } + ) + .add(() => { + this.mintingDAOCoin = false; + this.loadingNewSelection = false; + }); + } + }); + } + + disableMinting(): void { + if (this.myDAOCoin.MintingDisabled || this.disablingMinting) { + return; + } + SwalHelper.fire({ + target: this.globalVars.getTargetComponentSelector(), + title: "Disable Minting", + html: `Click confirm to disable minting for ${this.globalVars.loggedInUser?.ProfileEntryResponse?.Username} DAO coins. Please note, this is irreversible.`, + showCancelButton: true, + customClass: { + confirmButton: "btn btn-light", + cancelButton: "btn btn-light no", + }, + reverseButtons: true, + }).then((res: any) => { + if (res.isConfirmed) { + this.disablingMinting = true; + this.doDAOCoinTxn( + this.globalVars.loggedInUser?.PublicKeyBase58Check, + DAOCoinOperationTypeString.DISABLE_MINTING + ) + .subscribe( + (res) => { + this.myDAOCoin.MintingDisabled = true; + }, + (err) => { + this.globalVars._alertError(err.error.error); + console.error(err); + } + ) + .add(() => (this.disablingMinting = false)); + } + }); + } + + updateTransferRestrictionStatus(): void { + if ( + this.myDAOCoin.TransferRestrictionStatus === TransferRestrictionStatusString.PERMANENTLY_UNRESTRICTED || + this.updatingTransferRestrictionStatus || + this.transferRestrictionStatus === this.myDAOCoin.TransferRestrictionStatus + ) { + return; + } + SwalHelper.fire({ + target: this.globalVars.getTargetComponentSelector(), + title: "Update Transfer Restriction Status", + html: `Click confirm to update the transfer restriction status to ${this.getDisplayTransferRestrictionStatus( + this.transferRestrictionStatus + )} for ${this.globalVars.loggedInUser?.ProfileEntryResponse?.Username} DAO coins`, + showCancelButton: true, + customClass: { + confirmButton: "btn btn-light", + cancelButton: "btn btn-light no", + }, + reverseButtons: true, + }).then((res: any) => { + if (res.isConfirmed) { + this.updatingTransferRestrictionStatus = true; + this.doDAOCoinTxn( + this.globalVars.loggedInUser?.PublicKeyBase58Check, + DAOCoinOperationTypeString.UPDATE_TRANSFER_RESTRICTION_STATUS + ) + .subscribe( + (res) => { + this.myDAOCoin.TransferRestrictionStatus = this.transferRestrictionStatus; + }, + (err) => { + this.globalVars._alertError(err.error.error); + console.error(err); + } + ) + .add(() => (this.updatingTransferRestrictionStatus = false)); + } + }); + } + + burnDAOCoin(profilePublicKeyBase58Check: string): void { + if (this.burningDAOCoin || this.coinsToBurn <= 0) { + return; + } + SwalHelper.fire({ + target: this.globalVars.getTargetComponentSelector(), + title: "Burn DAO Coins", + html: `Click confirm to burn ${this.coinsToBurn} ${this.globalVars.loggedInUser?.ProfileEntryResponse?.Username} DAO coins`, + showCancelButton: true, + customClass: { + confirmButton: "btn btn-light", + cancelButton: "btn btn-light no", + }, + reverseButtons: true, + }).then((res: any) => { + if (res.isConfirmed) { + this.burningDAOCoin = true; + this.loadingNewSelection = true; + this.doDAOCoinTxn(this.globalVars.loggedInUser?.PublicKeyBase58Check, DAOCoinOperationTypeString.BURN) + .subscribe( + (res) => { + if (profilePublicKeyBase58Check === this.globalVars.loggedInUser?.PublicKeyBase58Check) { + this.myDAOCoin.CoinsInCirculationNanos = toBN(this.myDAOCoin.CoinsInCirculationNanos) + .add(toBN(this.globalVars.toHexNanos(this.coinsToBurn))) + .toString("hex"); + } + this.coinsToBurn = 0; + }, + (err) => { + this.globalVars._alertError(err.error.error); + console.error(err); + } + ) + .add(() => { + this.burningDAOCoin = false; + this.loadingNewSelection = false; + }); + } + }); + } + + doDAOCoinTxn(profilePublicKeyBase58Check: string, operationType: DAOCoinOperationTypeString): Observable { + if ( + profilePublicKeyBase58Check !== this.globalVars.loggedInUser?.PublicKeyBase58Check && + operationType !== DAOCoinOperationTypeString.BURN + ) { + return throwError("invalid dao coin operation - must be owner to perform " + operationType); + } + return this.backendApi.DAOCoin( + this.globalVars.localNode, + this.globalVars.loggedInUser?.PublicKeyBase58Check, + profilePublicKeyBase58Check, + operationType, + operationType === DAOCoinOperationTypeString.UPDATE_TRANSFER_RESTRICTION_STATUS + ? this.transferRestrictionStatus + : undefined, + operationType === DAOCoinOperationTypeString.MINT ? this.globalVars.toHexNanos(this.coinsToMint) : undefined, + operationType === DAOCoinOperationTypeString.BURN ? this.globalVars.toHexNanos(this.coinsToBurn) : undefined, + this.globalVars.defaultFeeRateNanosPerKB + ); + } + + unminedDeSoToolTip() { + return ( + "Mining in progress. Feel free to transact in the meantime.\n\n" + + "Mined balance:\n" + + this.globalVars.nanosToDeSo(this.globalVars.loggedInUser.BalanceNanos, 9) + + " DeSo.\n\n" + + "Unmined balance:\n" + + this.globalVars.nanosToDeSo(this.globalVars.loggedInUser.UnminedBalanceNanos, 9) + + " DeSo." + ); + } + + unminedCreatorCoinToolTip(creator: any) { + return ( + "Mining in progress. Feel free to transact in the meantime.\n\n" + + "Net unmined transactions:\n" + + this.globalVars.nanosToDeSo(creator.NetBalanceInMempool, 9) + + " DeSo.\n\n" + + "Balance w/unmined transactions:\n" + + this.globalVars.nanosToDeSo(creator.BalanceNanos, 9) + + " DeSo.\n\n" + ); + } + + usernameTruncationLength(): number { + return this.globalVars.isMobile() ? 14 : 20; + } + + emptyHodlerListMessage(): string { + return this.showDAOCoinHoldings ? "You don't hold any DAO coins" : "Your DAO doesn't have any coins yet."; + } + + _handleTabClick(tab: string) { + this.showDAOCoinHoldings = tab === DaoCoinsComponent.daoCoinsTab; + this.lastPage = Math.floor( + (this.showDAOCoinHoldings ? this.daoCoinHoldings : this.myDAOCapTable).length / DaoCoinsComponent.PAGE_SIZE + ); + this.activeTab = tab; + this.scrollerReset(); + } + + scrollerReset() { + this.infiniteScroller.reset(); + this.datasource.adapter.reset().then(() => this.datasource.adapter.check()); + } + + lastPage = null; + infiniteScroller: InfiniteScroller = new InfiniteScroller( + DaoCoinsComponent.PAGE_SIZE, + this.getPage.bind(this), + DaoCoinsComponent.WINDOW_VIEWPORT, + DaoCoinsComponent.BUFFER_SIZE, + DaoCoinsComponent.PADDING + ); + datasource: IDatasource> = this.infiniteScroller.getDatasource(); + + getPage(page: number) { + if (this.lastPage != null && page > this.lastPage) { + return []; + } + + const startIdx = page * DaoCoinsComponent.PAGE_SIZE; + const endIdx = (page + 1) * DaoCoinsComponent.PAGE_SIZE; + + return new Promise((resolve, reject) => { + resolve( + this.showDAOCoinHoldings + ? this.daoCoinHoldings.slice(startIdx, Math.min(endIdx, this.daoCoinHoldings.length)) + : this.myDAOCapTable.slice(startIdx, Math.min(endIdx, this.myDAOCapTable.length)) + ); + }); + } + + getDisplayTransferRestrictionStatus(transferRestrictionStatus: TransferRestrictionStatusString): string { + // If we're not provided a value, we assume it's unrestricted. + transferRestrictionStatus = transferRestrictionStatus || TransferRestrictionStatusString.UNRESTRICTED; + return transferRestrictionStatus + .split("_") + .map((status) => status.charAt(0).toUpperCase() + status.slice(1)) + .join(" ") + .replace("Dao", "DAO"); + } + + openTransferDAOCoinModal(creator: BalanceEntryResponse): void { + const modalDetails = this.modalService.show(TransferDAOCoinModalComponent, { + class: "modal-dialog-centered", + initialState: { balanceEntryResponse: creator }, + }); + const onHideEvent = modalDetails.onHide; + onHideEvent.subscribe((response) => { + if (response === "dao coins transferred") { + this.loadingNewSelection = true; + zip(this.loadMyDAOCoinHoldings(), this.loadMyDAOCapTable()).subscribe((res) => { + this.loadingNewSelection = false; + this._handleTabClick(this.activeTab); + }); + } + }); + } + + openBurnDAOCoinModal(creator: BalanceEntryResponse): void { + const modalDetails = this.modalService.show(BurnDaoCoinModalComponent, { + class: "modal-dialog-centered", + initialState: { balanceEntryResponse: creator }, + }); + const onHideEvent = modalDetails.onHide; + onHideEvent.subscribe((response) => { + if (response.startsWith("dao coins burned")) { + this.loadingNewSelection = true; + zip(this.loadMyDAOCoinHoldings(), this.loadMyDAOCapTable()).subscribe((res) => { + // If we burned our own coin in the modal, update the coins in circulation. + if (creator.CreatorPublicKeyBase58Check === this.globalVars.loggedInUser?.PublicKeyBase58Check) { + const splitResponse = response.split("|"); + if (splitResponse.length === 2) { + const burnAmountHex = splitResponse[1]; + this.myDAOCoin.CoinsInCirculationNanos = toBN(this.myDAOCoin.CoinsInCirculationNanos) + .sub(toBN(burnAmountHex)) + .toString("hex"); + } + } + this.loadingNewSelection = false; + this._handleTabClick(this.activeTab); + }); + } + }); + } +} diff --git a/src/app/dao-coins/transfer-dao-coin-modal/transfer-dao-coin-modal.component.html b/src/app/dao-coins/transfer-dao-coin-modal/transfer-dao-coin-modal.component.html new file mode 100644 index 000000000..e063a59cf --- /dev/null +++ b/src/app/dao-coins/transfer-dao-coin-modal/transfer-dao-coin-modal.component.html @@ -0,0 +1,47 @@ +
+
+ Transfer {{ balanceEntryResponse.ProfileEntryResponse?.Username }} DAO Coins +
+ + +
+ Your Balance: {{ globalVars.hexNanosToUnitString(balanceEntryResponse.BalanceNanosUint256) }} {{ balanceEntryResponse.ProfileEntryResponse?.Username }} DAO Coins +
+
+
+ Amount To Transfer +
+ +
+ + +
+ {{ validationError }} +
+
+
+ {{ backendErrors }} +
+
diff --git a/src/app/dao-coins/transfer-dao-coin-modal/transfer-dao-coin-modal.component.ts b/src/app/dao-coins/transfer-dao-coin-modal/transfer-dao-coin-modal.component.ts new file mode 100644 index 000000000..ca559c7c3 --- /dev/null +++ b/src/app/dao-coins/transfer-dao-coin-modal/transfer-dao-coin-modal.component.ts @@ -0,0 +1,127 @@ +import { Component, Input, OnInit } from "@angular/core"; +import { BsModalRef, BsModalService } from "ngx-bootstrap/modal"; +import { GlobalVarsService } from "../../global-vars.service"; +import { + BackendApiService, + BalanceEntryResponse, + ProfileEntryResponse, + TransferRestrictionStatusString, +} from "../../backend-api.service"; +import { toBN } from "web3-utils"; + +@Component({ + selector: "transfer-dao-coin-modal", + templateUrl: "./transfer-dao-coin-modal.component.html", +}) +export class TransferDAOCoinModalComponent implements OnInit { + @Input() balanceEntryResponse: BalanceEntryResponse; + + amountToTransfer: number = 0; + receiver: ProfileEntryResponse; + receiverIsDAOMember: boolean = false; + transferringDAOCoin: boolean = false; + backendErrors: string = ""; + validationErrors: string[] = []; + hideCreatorSearch: boolean = false; + constructor( + public bsModalRef: BsModalRef, + public modalService: BsModalService, + public globalVars: GlobalVarsService, + private backendApi: BackendApiService + ) {} + + ngOnInit(): void { + // If this DAO coin can only be transferred to the profile owner and we're not the profile owner, set the receiver + // to the profile owner and don't let them search. + if ( + this.balanceEntryResponse?.ProfileEntryResponse?.DAOCoinEntry?.TransferRestrictionStatus === + TransferRestrictionStatusString.PROFILE_OWNER_ONLY && + this.balanceEntryResponse?.CreatorPublicKeyBase58Check !== this.globalVars.loggedInUser?.PublicKeyBase58Check + ) { + this.hideCreatorSearch = true; + this.receiver = this.balanceEntryResponse?.ProfileEntryResponse; + } + } + + _handleCreatorSelectedInSearch(creator): void { + this.receiver = creator; + if ( + this.balanceEntryResponse.ProfileEntryResponse.DAOCoinEntry.TransferRestrictionStatus === + TransferRestrictionStatusString.DAO_MEMBERS_ONLY + ) { + this.backendApi + .IsHodlingPublicKey( + this.globalVars.localNode, + this.receiver.PublicKeyBase58Check, + this.balanceEntryResponse.CreatorPublicKeyBase58Check, + true + ) + .subscribe((res) => { + this.receiverIsDAOMember = res.IsHodling; + }) + .add(() => this.updateValidationErrors()); + } + this.updateValidationErrors(); + } + + transferDAOCoin(): void { + this.transferringDAOCoin = true; + this.backendErrors = ""; + this.backendApi + .TransferDAOCoin( + this.globalVars.localNode, + this.globalVars.loggedInUser?.PublicKeyBase58Check, + this.balanceEntryResponse.CreatorPublicKeyBase58Check, + this.receiver.PublicKeyBase58Check, + this.globalVars.toHexNanos(this.amountToTransfer), + this.globalVars.defaultFeeRateNanosPerKB + ) + .subscribe( + (res) => { + this.modalService.setDismissReason("dao coins transferred"); + this.bsModalRef.hide(); + }, + (err) => { + this.backendErrors = err.error.error; + console.error(err); + } + ) + .add(() => (this.transferringDAOCoin = false)); + } + + updateValidationErrors(): void { + let err: string[] = []; + if (this.receiver?.PublicKeyBase58Check === this.globalVars.loggedInUser?.PublicKeyBase58Check) { + err.push("Cannot transfer to yourself\n"); + } + if (this.receiver && this.amountToTransfer <= 0) { + err.push("Must transfer a non-zero amount\n"); + } + if ( + this.globalVars.unitToBNNanos(this.amountToTransfer || 0).gt(toBN(this.balanceEntryResponse.BalanceNanosUint256)) + ) { + err.push("Amount to transfer exceeds balance\n"); + } + if ( + this.receiver && + this.balanceEntryResponse.ProfileEntryResponse.DAOCoinEntry.TransferRestrictionStatus === + TransferRestrictionStatusString.PROFILE_OWNER_ONLY && + this.balanceEntryResponse.ProfileEntryResponse.PublicKeyBase58Check !== + this.globalVars.loggedInUser?.PublicKeyBase58Check && + this.balanceEntryResponse.ProfileEntryResponse.PublicKeyBase58Check !== this.receiver?.PublicKeyBase58Check + ) { + err.push("This DAO coin can only be transferred to or from the profile owner\n"); + } + if ( + this.receiver && + this.balanceEntryResponse.ProfileEntryResponse.DAOCoinEntry.TransferRestrictionStatus === + TransferRestrictionStatusString.DAO_MEMBERS_ONLY && + !this.receiverIsDAOMember && + this.balanceEntryResponse.ProfileEntryResponse.PublicKeyBase58Check !== + this.globalVars.loggedInUser?.PublicKeyBase58Check + ) { + err.push("This DAO coin can only be transferred to existing DAO members\n"); + } + this.validationErrors = err; + } +} diff --git a/src/app/global-vars.service.ts b/src/app/global-vars.service.ts index 813fec524..5b5a2d348 100755 --- a/src/app/global-vars.service.ts +++ b/src/app/global-vars.service.ts @@ -27,6 +27,8 @@ import { FeedComponent } from "./feed/feed.component"; import { BsModalRef, BsModalService } from "ngx-bootstrap/modal"; import Swal from "sweetalert2"; import Timer = NodeJS.Timer; +import {fromWei, Hex, toBN, toHex, toWei} from "web3-utils"; +import {BN} from "ethereumjs-util"; export enum ConfettiSvg { DIAMOND = "diamond", @@ -490,7 +492,7 @@ export class GlobalVarsService { * */ abbreviateNumber(value: number, decimals: number, formatUSD: boolean = false): string { let shortValue; - const suffixes = ["", "K", "M", "B", "T"]; + const suffixes = ["", "K", "M", "B", "t", "q", "Q"]; const suffixNum = Math.floor((("" + value.toFixed(0)).length - 1) / 3); if (suffixNum === 0) { // if the number is less than 1000, we should only show at most 2 decimals places @@ -518,6 +520,21 @@ export class GlobalVarsService { return this.formatUSD(this.nanosToUSDNumber(nanos), decimal); } + // Used to convert uint256 Hex balances for DAO coins to standard units. + hexNanosToUnitString(hexNanos: Hex, decimal: number = 4): string { + const result = fromWei(toBN(hexNanos), "gwei").toString(); + return this.abbreviateNumber(parseFloat(result), 4, false); + } + + // Converts a quantity of DAO coins to a Hex representing the number of nanos + toHexNanos(units: number): Hex { + return toHex(toWei(units.toString(), "gwei")); + } + + unitToBNNanos(units: number): BN { + return toBN(this.toHexNanos(units)); + } + isMobile(): boolean { // from https://stackoverflow.com/questions/1248081/how-to-get-the-browser-viewport-dimensions const viewportWidth = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0); diff --git a/src/app/left-bar/left-bar.component.html b/src/app/left-bar/left-bar.component.html index 6fc0eb403..c1a5934d7 100644 --- a/src/app/left-bar/left-bar.component.html +++ b/src/app/left-bar/left-bar.component.html @@ -81,6 +81,8 @@ > + +
diff --git a/src/app/notifications-page/notifications-list/notifications-list.component.ts b/src/app/notifications-page/notifications-list/notifications-list.component.ts index 5221d5842..39e154179 100644 --- a/src/app/notifications-page/notifications-list/notifications-list.component.ts +++ b/src/app/notifications-page/notifications-list/notifications-list.component.ts @@ -460,8 +460,50 @@ export class NotificationsListComponent { result.action = `${actorName} put an NFT on sale - you receive ${royaltyString} on the sale`; return result; } + } else if (txnMeta.TxnType === "DAO_COIN") { + const daoCoinMeta = txnMeta.DAOCoinTxindexMetadata; + if (!daoCoinMeta) { + return null; + } + switch (daoCoinMeta.OperationType) { + case "mint": { + result.action = `minted ${this.globalVars.hexNanosToUnitString(daoCoinMeta.CoinsToMintNanos)} ${ + daoCoinMeta.CreatorUsername + } DAO coin`; + result.icon = "fas fa-coins fc-green"; + return result; + } + case "burn": { + result.action = `${actorName} burned ${this.globalVars.hexNanosToUnitString(daoCoinMeta.CoinsToBurnNanos)} ${ + daoCoinMeta.CreatorUsername + } DAO coin`; + result.icon = "fa fa-fire fc-red"; + return result; + } + case "disable_minting": { + result.action = `${actorName} disabled minting for ${daoCoinMeta.CreatorUsername} DAO coin`; + result.icon = "fas fa-minus-circle fc-red"; + return result; + } + case "update_transfer_restriction_status": { + result.action = `${actorName} updated the transfer restriction status of ${daoCoinMeta.CreatorUsername} DAO coin to ${daoCoinMeta.TransferRestrictionStatus}`; + result.icon = "fas fa-pen-fancy"; + return result; + } + } + return null; + } else if (txnMeta.TxnType === "DAO_COIN_TRANSFER") { + const daoCoinTransferMeta = txnMeta.DAOCoinTransferTxindexMetadata; + if (!daoCoinTransferMeta) { + return null; + } + result.icon = "fas fa-money-bill-wave fc-blue"; + result.action = `${actorName} sent you ${this.globalVars.hexNanosToUnitString( + daoCoinTransferMeta.DAOCoinToTransferNanos, + 6 + )} ${daoCoinTransferMeta.CreatorUsername} coin`; + return result; } - // If we don't recognize the transaction type we return null return null; } diff --git a/src/app/wallet/wallet.component.ts b/src/app/wallet/wallet.component.ts index 8780800bb..67fe3f3ae 100644 --- a/src/app/wallet/wallet.component.ts +++ b/src/app/wallet/wallet.component.ts @@ -204,10 +204,8 @@ export class WalletComponent implements OnInit, OnDestroy { for (const holding of this.globalVars.loggedInUser.UsersYouHODL) { result += - this.globalVars.desoNanosYouWouldGetIfYouSold( - holding.BalanceNanos, - holding.ProfileEntryResponse.CoinEntry - ) || 0; + this.globalVars.desoNanosYouWouldGetIfYouSold(holding.BalanceNanos, holding.ProfileEntryResponse.CoinEntry) || + 0; } return result; @@ -266,7 +264,8 @@ export class WalletComponent implements OnInit, OnDestroy { return false; } return ( - balanceEntryResponse.ProfileEntryResponse.Username.toLowerCase() === this.balanceEntryToHighlight.ProfileEntryResponse.Username.toLowerCase() + balanceEntryResponse.ProfileEntryResponse.Username.toLowerCase() === + this.balanceEntryToHighlight.ProfileEntryResponse.Username.toLowerCase() ); }