Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for NFT transfers, burns, and acceptance of transfers #545

Merged
merged 3 commits into from
Jan 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,16 +157,16 @@ import { SanitizeVideoUrlPipe } from "../lib/pipes/sanitize-video-url-pipe";
import { AdminNodeFeesComponent } from "./admin/admin-node-fees/admin-node-fees.component";
import { AdminNodeAddFeesComponent } from "./admin/admin-node-fees/admin-node-add-fee/admin-node-add-fees.component";
import { FreeDesoMessageComponent } from "./free-deso-message/free-deso-message.component";
import { SupplyMonitoringStatsPageComponent } from "./supply-monitoring-stats-page/supply-monitoring-stats-page.component";
import { SupplyMonitoringStatsComponent } from "./supply-monitoring-stats-page/supply-monitoring-stats/supply-monitoring-stats.component";
import { TransferNftModalComponent } from "./transfer-nft-modal/transfer-nft-modal.component";
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";

// Modular Themes for DeSo by Carsen Klock @carsenk
import { ThemeModule } from "./theme/theme.module";
import { Theme } from "./theme/symbols";
import {
SupplyMonitoringStatsPageComponent
} from "./supply-monitoring-stats-page/supply-monitoring-stats-page.component";
import {
SupplyMonitoringStatsComponent
} from "./supply-monitoring-stats-page/supply-monitoring-stats/supply-monitoring-stats.component";
const lightTheme: Theme = { key: "light", name: "Light Theme" };
const darkTheme: Theme = { key: "dark", name: "Dark Theme" };
const icydarkTheme: Theme = { key: "icydark", name: "Icy Dark Theme" };
Expand Down Expand Up @@ -311,6 +311,10 @@ const greenishTheme: Theme = { key: "greenish", name: "Green Theme" };
SupplyMonitoringStatsPageComponent,
SupplyMonitoringStatsComponent,
FreeDesoMessageComponent,
TransferNftAcceptModalComponent,
TransferNftModalComponent,
NftBurnModalComponent,
NftSelectSerialNumberComponent,
],
imports: [
BrowserModule,
Expand Down
79 changes: 77 additions & 2 deletions src/app/backend-api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ export class BackendRoutes {
static RoutePathGetNextNFTShowcase = "/api/v0/get-next-nft-showcase";
static RoutePathGetNFTCollectionSummary = "/api/v0/get-nft-collection-summary";
static RoutePathGetNFTEntriesForPostHash = "/api/v0/get-nft-entries-for-nft-post";
static RoutePathTransferNFT = "/api/v0/transfer-nft";
static RoutePathAcceptNFTTransfer = "/api/v0/accept-nft-transfer";
static RoutePathBurnNFT = "/api/v0/burn-nft";

// ETH
static RoutePathSubmitETHTx = "/api/v0/submit-eth-tx";
Expand Down Expand Up @@ -154,7 +157,7 @@ export class BackendRoutes {
// Supply Monitoring endpoints
static RoutePathGetTotalSupply = "/api/v0/total-supply";
static RoutePathGetRichList = "/api/v0/rich-list";
static RoutePathGetCountKeysWithDESO = "/api/v0/count-keys-with-deso"
static RoutePathGetCountKeysWithDESO = "/api/v0/count-keys-with-deso";
}

export class Transaction {
Expand Down Expand Up @@ -334,6 +337,7 @@ export class NFTEntryResponse {
PostEntryResponse: PostEntryResponse | undefined;
SerialNumber: number;
IsForSale: boolean;
IsPending?: boolean;
MinBidAmountNanos: number;
LastAcceptedBidAmountNanos: number;

Expand Down Expand Up @@ -977,6 +981,75 @@ export class BackendApiService {
return this.signAndSubmitTransaction(endpoint, request, UpdaterPublicKeyBase58Check);
}

TransferNFT(
endpoint: string,
SenderPublicKeyBase58Check: string,
ReceiverPublicKeyBase58Check: string,
NFTPostHashHex: string,
SerialNumber: number,
UnencryptedUnlockableText: string,
MinFeeRateNanosPerKB: number
): Observable<any> {
let request = UnencryptedUnlockableText
? this.identityService.encrypt({
...this.identityService.identityServiceParamsForKey(SenderPublicKeyBase58Check),
recipientPublicKey: ReceiverPublicKeyBase58Check,
message: UnencryptedUnlockableText,
})
: of({ encryptedMessage: "" });
request = request.pipe(
switchMap((encrypted) => {
const EncryptedUnlockableText = encrypted.encryptedMessage;
return this.post(endpoint, BackendRoutes.RoutePathTransferNFT, {
SenderPublicKeyBase58Check,
ReceiverPublicKeyBase58Check,
NFTPostHashHex,
SerialNumber,
EncryptedUnlockableText,
MinFeeRateNanosPerKB,
}).pipe(
map((request) => {
return { ...request };
})
);
})
);

return this.signAndSubmitTransaction(endpoint, request, SenderPublicKeyBase58Check);
}

AcceptNFTTransfer(
endpoint: string,
UpdaterPublicKeyBase58Check: string,
NFTPostHashHex: string,
SerialNumber: number,
MinFeeRateNanosPerKB: number
): Observable<any> {
const request = this.post(endpoint, BackendRoutes.RoutePathAcceptNFTTransfer, {
UpdaterPublicKeyBase58Check,
NFTPostHashHex,
SerialNumber,
MinFeeRateNanosPerKB,
});
return this.signAndSubmitTransaction(endpoint, request, UpdaterPublicKeyBase58Check);
}

BurnNFT(
endpoint: string,
UpdaterPublicKeyBase58Check: string,
NFTPostHashHex: string,
SerialNumber: number,
MinFeeRateNanosPerKB: number
): Observable<any> {
const request = this.post(endpoint, BackendRoutes.RoutePathBurnNFT, {
UpdaterPublicKeyBase58Check,
NFTPostHashHex,
SerialNumber,
MinFeeRateNanosPerKB,
});
return this.signAndSubmitTransaction(endpoint, request, UpdaterPublicKeyBase58Check);
}

DecryptUnlockableTexts(
ReaderPublicKeyBase58Check: string,
UnlockableNFTEntryResponses: NFTEntryResponse[]
Expand Down Expand Up @@ -1016,12 +1089,14 @@ export class BackendApiService {
endpoint: string,
UserPublicKeyBase58Check: string,
ReaderPublicKeyBase58Check: string,
IsForSale: boolean | null = null
IsForSale: boolean | null = null,
IsPending: boolean | null = null
): Observable<any> {
return this.post(endpoint, BackendRoutes.RoutePathGetNFTsForUser, {
UserPublicKeyBase58Check,
ReaderPublicKeyBase58Check,
IsForSale,
IsPending,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ export class CloseNftAuctionModalComponent {
.subscribe(
(res) => {
// Hide this modal and open the next one.
this.bsModalRef.hide();
this.modalService.setDismissReason("auction cancelled");
this.bsModalRef.hide();
},
(err) => {
console.error(err);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component, Input } from "@angular/core";
import { BsModalRef } from "ngx-bootstrap/modal";
import { BsModalRef, BsModalService } from "ngx-bootstrap/modal";
import { GlobalVarsService } from "../global-vars.service";
import { BackendApiService, NFTEntryResponse, PostEntryResponse } from "../backend-api.service";
import { concatMap, last, map } from "rxjs/operators";
Expand All @@ -25,6 +25,7 @@ export class CreateNftAuctionModalComponent {
private backendApi: BackendApiService,
public globalVars: GlobalVarsService,
public bsModalRef: BsModalRef,
private modalService: BsModalService,
private router: Router
) {}

Expand Down Expand Up @@ -70,6 +71,7 @@ export class CreateNftAuctionModalComponent {
.subscribe(
(res) => {
this.router.navigate(["/" + this.globalVars.RouteNames.NFT + "/" + this.post.PostHashHex]);
this.modalService.setDismissReason("auction created");
this.bsModalRef.hide();
},
(err) => {
Expand All @@ -84,6 +86,7 @@ export class CreateNftAuctionModalComponent {
return this.nftEntryResponses.filter(
(nftEntryResponse) =>
!nftEntryResponse.IsForSale &&
!nftEntryResponse.IsPending &&
nftEntryResponse.OwnerPublicKeyBase58Check === this.globalVars.loggedInUser?.PublicKeyBase58Check
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,32 @@
</ng-template>
</div>
</div>
<div
*ngIf="
!showProfileAsReserved && !isLoading && !nftResponse?.length && activeTab === CreatorProfileNftsComponent.TRANSFERABLE
"
class="p-15px"
>
<div class="background-color-grey p-35px br-12px d-flex flex-row align-items-center" style="text-align: center">
<span *ngIf="profileBelongsToLoggedInUser(); else elseMissingPostBlock">No transferable NFTs right now.</span>
<ng-template #elseMissingPostBlock>
<span>@{{ profile.Username }} has no transferable NFTs yet.</span>
</ng-template>
</div>
</div>
<div
*ngIf="
!showProfileAsReserved && !isLoading && !nftResponse?.length && activeTab === CreatorProfileNftsComponent.MY_PENDING_TRANSFERS
"
class="p-15px"
>
<div class="background-color-grey p-35px br-12px d-flex flex-row align-items-center" style="text-align: center">
<span *ngIf="profileBelongsToLoggedInUser(); else elseMissingPostBlock">No pending NFTs right now.</span>
<ng-template #elseMissingPostBlock>
<span>@{{ profile.Username }} has no pending NFTs yet.</span>
</ng-template>
</div>
</div>
<div
*ngIf="
!showProfileAsReserved && !isLoading && !myBids?.length && activeTab === CreatorProfileNftsComponent.MY_BIDS
Expand All @@ -70,6 +96,12 @@
<div *ngIf="activeTab === CreatorProfileNftsComponent.FOR_SALE && nftResponse?.length">
NFTs that @{{ profile.Username }} is currently selling
</div>
<div *ngIf="activeTab === CreatorProfileNftsComponent.TRANSFERABLE && nftResponse?.length">
NFTs that @{{ profile.Username }} can transfer
</div>
<div *ngIf="activeTab === CreatorProfileNftsComponent.MY_PENDING_TRANSFERS && nftResponse?.length">
Transferred NFTs pending acceptance by @{{ profile.Username }}
</div>
</div>
<div *ngIf="!globalVars.hasUserBlockedCreator(profile.PublicKeyBase58Check)">
<div
Expand All @@ -96,6 +128,7 @@
[cardStyle]="true"
[profilePublicKeyBase58Check]="profile.PublicKeyBase58Check"
[isForSaleOnly]="activeTab === CreatorProfileNftsComponent.FOR_SALE"
[acceptNFT]="activeTab === CreatorProfileNftsComponent.MY_PENDING_TRANSFERS"
(userBlocked)="userBlocked()"
></feed-post>
<div *ngIf="activeTab === CreatorProfileNftsComponent.MY_BIDS && nftEntry.PostEntryResponse">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,25 @@ export class CreatorProfileNftsComponent implements OnInit {
static FOR_SALE = "For Sale";
static MY_BIDS = "My Bids";
static MY_GALLERY = "Gallery";
static TRANSFERABLE = "Transferable";
static MY_PENDING_TRANSFERS = "Pending Transfers";
tabs = [CreatorProfileNftsComponent.FOR_SALE, CreatorProfileNftsComponent.MY_GALLERY];
activeTab: string;

nftTabMap = {
my_bids: CreatorProfileNftsComponent.MY_BIDS,
for_sale: CreatorProfileNftsComponent.FOR_SALE,
my_gallery: CreatorProfileNftsComponent.MY_GALLERY,
transferable: CreatorProfileNftsComponent.TRANSFERABLE,
my_pending_transfers: CreatorProfileNftsComponent.MY_PENDING_TRANSFERS,
};

nftTabInverseMap = {
[CreatorProfileNftsComponent.FOR_SALE]: "for_sale",
[CreatorProfileNftsComponent.MY_BIDS]: "my_bids",
[CreatorProfileNftsComponent.MY_GALLERY]: "my_gallery",
[CreatorProfileNftsComponent.TRANSFERABLE]: "transferable",
[CreatorProfileNftsComponent.MY_PENDING_TRANSFERS]: "my_pending_transfers",
};

CreatorProfileNftsComponent = CreatorProfileNftsComponent;
Expand All @@ -68,13 +74,19 @@ export class CreatorProfileNftsComponent implements OnInit {
) {}

ngOnInit(): void {
if (this.globalVars.loggedInUser?.PublicKeyBase58Check === this.profile.PublicKeyBase58Check) {
this.tabs.push(CreatorProfileNftsComponent.MY_BIDS);
if (this.profileBelongsToLoggedInUser()) {
this.tabs.push(
CreatorProfileNftsComponent.MY_BIDS,
CreatorProfileNftsComponent.MY_PENDING_TRANSFERS,
CreatorProfileNftsComponent.TRANSFERABLE
);
}
this.route.queryParams.subscribe((queryParams) => {
if (queryParams.nftTab && queryParams.nftTab in this.nftTabMap) {
if (
queryParams.nftTab === this.nftTabInverseMap[CreatorProfileNftsComponent.MY_BIDS] &&
(queryParams.nftTab === this.nftTabInverseMap[CreatorProfileNftsComponent.MY_BIDS] ||
queryParams.nftTab === this.nftTabInverseMap[CreatorProfileNftsComponent.TRANSFERABLE] ||
queryParams.nftTab === this.nftTabInverseMap[CreatorProfileNftsComponent.MY_PENDING_TRANSFERS]) &&
this.globalVars.loggedInUser?.PublicKeyBase58Check !== this.profile.PublicKeyBase58Check
) {
this.updateNFTTabParam(CreatorProfileNftsComponent.MY_GALLERY);
Expand Down Expand Up @@ -121,13 +133,14 @@ export class CreatorProfileNftsComponent implements OnInit {
);
}

getNFTs(isForSale: boolean | null = null): Subscription {
getNFTs(isForSale: boolean | null = null, isPending: boolean | null = null): Subscription {
return this.backendApi
.GetNFTsForUser(
this.globalVars.localNode,
this.profile.PublicKeyBase58Check,
this.globalVars.loggedInUser?.PublicKeyBase58Check,
isForSale
isForSale,
isPending
)
.subscribe(
(res: {
Expand All @@ -136,13 +149,16 @@ export class CreatorProfileNftsComponent implements OnInit {
this.nftResponse = [];
for (const k in res.NFTsMap) {
const responseElement = res.NFTsMap[k];
// Exclude NFTs created by profile from Gallery and don't show pending NFTs in galley.
if (
(this.activeTab === CreatorProfileNftsComponent.MY_GALLERY &&
responseElement.PostEntryResponse.PosterPublicKeyBase58Check !== this.profile.PublicKeyBase58Check) ||
this.activeTab === CreatorProfileNftsComponent.FOR_SALE
this.activeTab === CreatorProfileNftsComponent.MY_GALLERY &&
(responseElement.PostEntryResponse.PosterPublicKeyBase58Check === this.profile.PublicKeyBase58Check ||
responseElement.NFTEntryResponses.filter((nftEntryResponse) => !nftEntryResponse.IsPending).length ===
0)
) {
this.nftResponse.push(responseElement);
continue;
}
this.nftResponse.push(responseElement);
}
this.lastPage = Math.floor(this.nftResponse.length / CreatorProfileNftsComponent.PAGE_SIZE);
return this.nftResponse;
Expand Down Expand Up @@ -218,7 +234,7 @@ export class CreatorProfileNftsComponent implements OnInit {
this.resetDatasource(event);
});
} else {
return this.getNFTs(this.getIsForSaleValue()).add(() => {
return this.getNFTs(this.getIsForSaleValue(), this.getIsPendingValue()).add(() => {
this.resetDatasource(event);
});
}
Expand Down Expand Up @@ -290,6 +306,25 @@ export class CreatorProfileNftsComponent implements OnInit {
}

getIsForSaleValue(): boolean | null {
return this.activeTab === CreatorProfileNftsComponent.MY_GALLERY ? null : true;
if (this.activeTab === CreatorProfileNftsComponent.FOR_SALE) {
return true;
} else if (this.activeTab === CreatorProfileNftsComponent.TRANSFERABLE) {
return false;
} else {
return null;
}
}

getIsPendingValue(): boolean | null {
if (this.activeTab === CreatorProfileNftsComponent.MY_PENDING_TRANSFERS) {
return true;
} else if (
this.activeTab === CreatorProfileNftsComponent.MY_GALLERY ||
this.activeTab === CreatorProfileNftsComponent.TRANSFERABLE
) {
return false;
} else {
return null;
}
}
}
Loading