Skip to content

Commit

Permalink
Ln/dao coins (#548)
Browse files Browse the repository at this point in the history
* save current progress on DAO coin UI

* add support for transferring DAO coins in the UI, add inputs for other fields for DAO coin transaction

* add burn support, add DAO coin tab to profile, use number abbreviation to keep DAO coin numbers manageable, move utility func for parsing hex balances to global vars

* add some frontend validation in transfer modal

* fix up DAO modals - add balances and validations, hit isHodling endpoint if transfer restriction status is DAO Members only

* fix alignment on DAO coin page

* disable mint and burn if the amount is less than or equal to 0

* add notifications for DAO coin txns

* add sweet alerts before DAO actions, only show profile owner if transfer restriction status is profile owner only and logged in user is not profile owner

* address TGS feedback

* Fix errors when user does not have DAO coin yet
  • Loading branch information
lazynina authored Jan 24, 2022
1 parent 02316e0 commit 60cf557
Show file tree
Hide file tree
Showing 21 changed files with 1,305 additions and 23 deletions.
7 changes: 4 additions & 3 deletions src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 = [
Expand All @@ -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" },
Expand Down
8 changes: 8 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -315,6 +319,10 @@ const greenishTheme: Theme = { key: "greenish", name: "Green Theme" };
TransferNftModalComponent,
NftBurnModalComponent,
NftSelectSerialNumberComponent,
DaoCoinsComponent,
DaoCoinsPageComponent,
TransferDAOCoinModalComponent,
BurnDaoCoinModalComponent,
],
imports: [
BrowserModule,
Expand Down
90 changes: 88 additions & 2 deletions src/app/backend-api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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";
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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;
Expand All @@ -187,6 +200,7 @@ export class ProfileEntryResponse {
CoinsInCirculationNanos: number;
CreatorBasisPoints: number;
};
DAOCoinEntry?: DAOCoinEntryResponse;
CoinPriceDeSoNanos?: number;
StakeMultipleBasisPoints?: number;
PublicKeyBase58Check?: string;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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",
})
Expand Down Expand Up @@ -1340,17 +1370,33 @@ export class BackendApiService {
LastPublicKeyBase58Check: string,
NumToFetch: number,
FetchHodlings: boolean = false,
FetchAll: boolean = false
): Observable<any> {
FetchAll: boolean = false,
IsDAOCoin: boolean = false
): Observable<{ Hodlers: BalanceEntryResponse[]; LastPublicKeyBase58Check: string }> {
return this.post(endpoint, BackendRoutes.RoutePathGetHodlersForPublicKey, {
PublicKeyBase58Check,
Username,
LastPublicKeyBase58Check,
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
Expand Down Expand Up @@ -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<any> {
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<any> {
const request = this.post(endpoint, BackendRoutes.RoutePathTransferDAOCoin, {
SenderPublicKeyBase58Check,
ProfilePublicKeyBase58CheckOrUsername,
ReceiverPublicKeyBase58CheckOrUsername,
DAOCoinToTransferNanos,
MinFeeRateNanosPerKB,
});
return this.signAndSubmitTransaction(endpoint, request, SenderPublicKeyBase58Check);
}

BlockPublicKey(
endpoint: string,
PublicKeyBase58Check: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

<!-- Feed Selector -->
<tab-selector
[tabs]="['Posts', 'Creator Coin', 'Diamonds', 'NFTs']"
[tabs]="['Posts', 'Creator Coin', 'DAO Coin', 'Diamonds', 'NFTs']"
[activeTab]="activeTab"
(tabClick)="_handleTabClick($event)"
></tab-selector>
Expand Down Expand Up @@ -83,7 +83,7 @@
<div class="w-100 d-flex flex-column" *ngIf="activeTab == 'Creator Coin' && !loading">
<div class="w-100 d-flex justify-content-start px-15px fs-15px">
<div class="container border-bottom border-color-grey font-weight-bold fs-20px pl-0px py-15px">
Holders of ${{ profile.Username }} coin
Holders of ${{ profile.Username }}'s Creator Coin
</div>
</div>

Expand All @@ -109,6 +109,26 @@
<div class="w-100 p-35px"></div>
</div>

<!-- DAO Coin Info -->
<div class="w-100 d-flex flex-column" *ngIf="activeTab == 'DAO Coin' && !loading">
<div class="w-100 d-flex justify-content-start px-15px fs-15px">
<div class="container border-bottom border-color-grey font-weight-bold fs-20px pl-0px py-15px">
Holders of ${{ profile.Username }}'s DAO Coin
</div>
</div>

<div>
<div class="container fs-15px flex-grow-1">
<div class="row no-gutters border-bottom border-color-grey fc-muted">
<div class="col-6 d-flex py-15px mb-0">Username or PubKey</div>
<div class="col-6 py-15px mb-0">Coins Held</div>
</div>
<creator-profile-hodlers [profile]="profile" [isDAOCoin]="true"></creator-profile-hodlers>
</div>
</div>
<div class="w-100 p-35px"></div>
</div>

<!-- Diamonds -->
<div class="w-100 d-flex flex-column" *ngIf="activeTab == 'Diamonds' && !loading">
<creator-diamonds [profile]="profile"></creator-diamonds>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
};
Expand All @@ -29,6 +30,7 @@ export class CreatorProfileDetailsComponent implements OnInit {
"Creator Coin": "creator-coin",
Diamonds: "diamonds",
NFTs: "nfts",
"DAO Coin": "dao",
};
appData: GlobalVarsService;
userName: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
<div *ngIf="!datasource.adapter.isLoading && datasource.adapter.itemsCount === 0" class="row no-gutters pt-10px">
<div class="d-flex align-items-center" style="margin-bottom: 0">
No one owns ${{ profile.Username }} coin yet.&nbsp;
<a [routerLink]="['/' + globalVars.RouteNames.USER_PREFIX, profile.Username, 'buy']">Be the first!</a>
No one owns ${{ profile.Username }}'s {{ isDAOCoin ? "DAO" : "Creator" }} Coin yet.&nbsp;
<a
[routerLink]="['/' + globalVars.RouteNames.USER_PREFIX, profile.Username, 'buy']"
*ngIf="!isDAOCoin"
>
Be the first!
</a>
</div>
</div>
<simple-center-loader *ngIf="datasource.adapter.isLoading && loadingFirstPage"></simple-center-loader>
Expand All @@ -23,6 +28,7 @@
matTooltipClass="global__mat-tooltip global__mat-tooltip-font-size"
[matTooltip]="getTooltipForRow(row)"
#tooltip="matTooltip"
*ngIf="!isDAOCoin"
>
<div *ngIf="!row.HasPurchased" class="text-grey9" style="font-size: 10px">
<i class="fas fa-exchange-alt pr-5px"></i>
Expand Down Expand Up @@ -80,6 +86,7 @@
matTooltipClass="global__mat-tooltip global__mat-tooltip-font-size"
[matTooltip]="getTooltipForRow(row)"
#tooltip="matTooltip"
*ngIf="!isDAOCoin"
>
<div *ngIf="!row.HasPurchased" class="text-grey9" style="font-size: 10px">
<i class="fas fa-exchange-alt pr-5px"></i>
Expand All @@ -99,20 +106,23 @@
</div>
</a>
</div>
<div class="col-3 d-flex align-items-center mb-0">
{{ (row.BalanceNanos / 1e9).toFixed(4) }}
<div class="d-flex align-items-center mb-0" [ngClass]="{ 'col-6': isDAOCoin, 'col-3': !isDAOCoin }">
{{ isDAOCoin ? globalVars.hexNanosToUnitString(row.BalanceNanosUint256) : (row.BalanceNanos / 1e9).toFixed(4) }}
</div>
<div class="col-3 d-flex align-items-center mb-0">
<div class="col-3 d-flex align-items-center mb-0" *ngIf="!isDAOCoin">
≈ {{ globalVars.creatorCoinNanosToUSDNaive(row.BalanceNanos, profile.CoinPriceDeSoNanos, true) }}
</div>
</div>

<div class="row no-gutters font-weight-bold" *ngIf="row.totalRow">
<div class="col-6 py-15px mb-0">Total</div>
<div class="col-3 py-15px mb-0">
{{ (profile.CoinEntry.CoinsInCirculationNanos / 1e9).toFixed(4) }}
<div class="col-3 py-15px mb-0" [ngClass]="{ 'col-6': isDAOCoin, 'col-3': !isDAOCoin }">
{{ isDAOCoin ?
globalVars.hexNanosToUnitString(profile.DAOCoinEntry.CoinsInCirculationNanos) :
(profile.CoinEntry.CoinsInCirculationNanos / 1e9).toFixed(4)
}}
</div>
<div class="col-3 py-15px mb-0">
<div class="col-3 py-15px mb-0" *ngIf="!isDAOCoin">
{{
globalVars.creatorCoinNanosToUSDNaive(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -40,7 +41,8 @@ export class CreatorProfileHodlersComponent {
lastPublicKeyBase58Check,
CreatorProfileHodlersComponent.PAGE_SIZE,
false,
false
false,
this.isDAOCoin,
)
.toPromise()
.then((res) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<div app-theme class="nft-modal-container p-15px d-flex flex-column">
<div class="fs-20px">
Burn {{ balanceEntryResponse.ProfileEntryResponse?.Username }} DAO Coins
</div>
<div class="pt-15px">
Your Balance: {{ globalVars.hexNanosToUnitString(balanceEntryResponse.BalanceNanosUint256) }} {{ balanceEntryResponse.ProfileEntryResponse?.Username }} DAO Coins
</div>
<div class="input-group py-15px">
<div class="input-group-prepend">
<span class="input-group-text">Amount To Burn</span>
</div>
<input
[(ngModel)]="amountToBurn"
(ngModelChange)="updateValidationErrors()"
class="form-control fs-15px text-right d-inline-block"
type="number"
min="0"
placeholder="0"
[disabled]="burningDAOCoin"/>
</div>
<button
class="btn btn-primary font-weight-bold br-12px"
style="height: 36px; width: 180px; line-height: 15px"
(click)="burnDAOCoin()"
[disabled]="burningDAOCoin || validationErrors.length"
>Burn</button>
<ng-container *ngIf="validationErrors.length">
<div *ngFor="let validationError of validationErrors" class="pt-5px fc-red">
{{ validationError }}
</div>
</ng-container>
<div class="fc-red pt-5px" *ngIf="backendErrors">
{{ backendErrors }}
</div>
</div>
Loading

0 comments on commit 60cf557

Please sign in to comment.