Commit 62df98c9 authored by John Doe's avatar John Doe
Browse files

Upload Frontend.

parent 8b6a1aef
Pipeline #5 failed with stages
in 0 seconds
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AssetsComponent } from './assets.component';
describe('AssetsComponent', () => {
let component: AssetsComponent;
let fixture: ComponentFixture<AssetsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ AssetsComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(AssetsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
import { Tokens } from '../../../../core/enums';
import { TOKENS_MAP } from '../../../../core/helpers';
import { TokenType } from '../../../../core/types';
@Component({
selector: 'os-assets',
templateUrl: './assets.component.html',
styleUrls: ['./assets.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AssetsComponent implements OnInit {
public assets!: TokenType[];
constructor(
private readonly _dynamicDialogRef: DynamicDialogRef,
private readonly _dynamicDialogConfig: DynamicDialogConfig,
) {
}
public ngOnInit(): void {
this.assets = Object.keys(TOKENS_MAP)
.filter((key) => key !== Tokens.XFT)
.map((key) => TOKENS_MAP[key]);
}
public onSelectAsset(selectedAsset: TokenType): void {
this._dynamicDialogRef.close(selectedAsset);
}
}
<div class="commitments">
<div class="commitments__header">
<div class="commitments__text">My Commitments</div>
<div class="commitments__zero-balances">
<span class="material-icons">check_circle</span>
<!-- <span class="material-icons">check_circle_outline</span>-->
<span>Show zero balances</span>
</div>
</div>
<ngx-simplebar [options]="{ autoHide: false }"
class="commitments__container">
<p-table [value]="token?.commitments"
[(selection)]="selectedCommitments"
dataKey="id"
class="commitments__content">
<ng-template pTemplate="header">
<tr>
<th>
<p-tableHeaderCheckbox></p-tableHeaderCheckbox>
</th>
<th>
<div>
<span>Commitment #</span>
<div class="commitments__zero-balances">
<span>Token amount</span>
<span class="pi pi-sort-down"></span>
<!--<span class="pi pi-sort-up"></span>-->
</div>
</div>
</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-commitment>
<tr>
<td>
<p-tableCheckbox [value]="commitment"></p-tableCheckbox>
</td>
<td>
<div>
<div class="commitments__text">{{ commitment.id }}</div>
<div class="commitments__balance">
<ng-container [ngTemplateOutlet]="tokenRef"
[ngTemplateOutletContext]="{ item: commitment }">
</ng-container>
<span class="commitments__balance-usd">${{ commitment.USD | normalizeBN }}</span>
</div>
</div>
</td>
</tr>
</ng-template>
</p-table>
</ngx-simplebar>
<div class="commitments__footer">
<div class="commitments__text">Selected commitments balance:</div>
<div class="commitments__balance">
<ng-container [ngTemplateOutlet]="tokenRef"
[ngTemplateOutletContext]="{item: { amount: sumToken }}">
</ng-container>
<span class="commitments__balance-usd">${{ sumUSD | normalizeBN }}</span>
</div>
</div>
<button (click)="onDoneClick()"
[disabled]="!selectedCommitments?.length"
pButton
type="button"
label="Done"
class="commitments__button">
</button>
</div>
<ng-template #tokenRef let-item="item">
<div class="token">
<div class="token__icon">
<img [src]="token?.icon" alt="">
</div>
<span class="token__amount">{{ item.amount | normalizeBN }}</span>
</div>
</ng-template>
@import "~src/styles/variables";
:host {
display: block;
}
.commitments {
display: flex;
flex-direction: column;
& &__container {
max-height: 400px;
margin: 0 -24px;
}
& &__header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
& &__zero-balances {
display: inline-flex;
align-items: center;
gap: 8px;
color: $offshift-primary-color;
cursor: pointer;
> span.material-icons {
font-size: 16px;
}
> span:not(.material-icons) {
font-weight: 600;
font-size: 12px;
line-height: 19px;
}
}
& &__content {
tr > th > div > span {
opacity: 0.7;
color: #1B0D3D;
}
tr > th > div > .commitments__zero-balances {
color: $offshift-primary-color;
cursor: pointer;
}
tr > th > div > span,
tr > th > div > div {
font-weight: 600;
font-size: 12px;
line-height: 19px;
}
tr > th,
tr > td {
background-color: #FFFFFF;
border: 0;
}
tr > th:first-of-type,
tr > td:first-of-type {
width: 20px;
padding-right: 0;
padding-left: 24px;
}
tr > th:last-of-type,
tr > td:last-of-type {
padding-right: 24px;
padding-top: 6px;
padding-bottom: 6px;
}
tr > th:last-of-type > div,
tr > td:last-of-type > div {
display: flex;
align-items: center;
justify-content: space-between;
}
tr > td:last-of-type > div {
background: rgba(27, 13, 61, 0.05);
border-radius: 4px;
padding: 16px;
}
}
& &__footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 0;
}
& &__text {
font-weight: 600;
font-size: 16px;
line-height: 26px;
color: #1B0D3D;
}
& &__balance {
display: flex;
flex-direction: column;
align-items: flex-end;
}
& &__balance-usd {
font-weight: 600;
font-size: 12px;
line-height: 19px;
color: #1B0D3D;
opacity: 0.7;
}
& &__button {
width: 100%;
}
}
.token {
display: inline-flex;
align-items: center;
gap: 4px;
& &__icon {
width: 16px;
height: 16px;
> img {
width: 100%;
height: 100%;
}
}
& &__amount {
font-weight: 600;
font-size: 16px;
line-height: 26px;
color: #1B0D3D;
}
}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CommitmentsComponent } from './commitments.component';
describe('CommitmentsComponent', () => {
let component: CommitmentsComponent;
let fixture: ComponentFixture<CommitmentsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ CommitmentsComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(CommitmentsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
import { BalanceCommitment, OutputParamsBalance } from '../../../../core/interfaces/contract-service-methods';
import { ContractService } from '../../../../core/services/contract.service';
import { selectAccountAddress } from '../../../../core/selectors';
import { Store } from '@ngrx/store';
import { BaseComponent } from '../../../../base.component';
import { firstValueFrom } from 'rxjs';
import { valueToBigNumber } from '../../../../core/helpers';
@Component({
selector: 'os-commitments',
templateUrl: './commitments.component.html',
styleUrls: ['./commitments.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CommitmentsComponent extends BaseComponent {
public token: OutputParamsBalance;
public selectedCommitments: BalanceCommitment[];
constructor(
store: Store,
private readonly _dynamicDialogRef: DynamicDialogRef,
private readonly _dynamicDialogConfig: DynamicDialogConfig,
private readonly _contractService: ContractService,
) {
super(store);
this.token = this._dynamicDialogConfig.data.token;
this.selectedCommitments = [this._dynamicDialogConfig.data.selectedCommitment];
}
public get sumToken(): string {
return this._calculateSelectedSum('amount');
}
public get sumUSD(): string {
return this._calculateSelectedSum('USD');
}
public async onDoneClick(): Promise<void> {
if (!this.selectedCommitments?.length) {
return;
}
if (this.selectedCommitments.length > 1) {
const accountAddress = await firstValueFrom(this.store.select(selectAccountAddress));
const commitmentIds = this.selectedCommitments.map((c) => Number(c.id));
const callback = async (error: any, hash: string) => {
console.log(error);
console.log(hash);
};
const { transactionHash } = await this._contractService
.groupCommitment(accountAddress, commitmentIds, callback);
const commitment = await this._contractService
.groupCommitmentsSpent(accountAddress, transactionHash, commitmentIds);
this._dynamicDialogRef.close(commitment);
} else {
this._dynamicDialogRef.close(this.selectedCommitments[0]);
}
}
private _calculateSelectedSum(prop: 'amount' | 'USD'): string {
if (!this.selectedCommitments?.length) {
return '0';
}
return this.selectedCommitments
.map((c) => (c as any)[prop])
.reduce((p, c) => {
p = p.plus(c);
return p;
}, valueToBigNumber(0))
.toString();
}
}
<div class="settings">
<!--<div class="settings__label">
<span>Slippage tolerance</span>
<span class="pi pi-info-circle"></span>
</div>
<p-selectButton [options]="slippageTolerance"
[(ngModel)]="selectedSlippageTolerance"
[style]="{ width: '100%', display: 'flex' }"
optionLabel="label"
optionValue="value"
class="settings__slippage">
</p-selectButton>
<p-inputNumber [(ngModel)]="customSlippageTolerance"
[step]="0.1"
[minFractionDigits]="1"
[maxFractionDigits]="5"
suffix="%"
[style]="{ width: '100%' }"
class="settings__slippage-input">
</p-inputNumber>-->
<div class="settings__label">
<span>Transaction speed (GWEI)</span>
<span class="pi pi-info-circle"></span>
</div>
<p-selectButton [options]="transactionSpeed"
[(ngModel)]="selectedTransactionSpeed"
[style]="{ width: '100%', display: 'flex' }"
optionLabel="label"
optionValue="value"
class="settings__transaction">
</p-selectButton>
</div>
@import "~src/styles/variables";
:host {
display: block;
}
.settings {
display: flex;
flex-direction: column;
& &__label {
display: inline-flex;
align-items: center;
gap: 8px;
font-weight: 600;
font-size: 14px;
line-height: 22px;
color: #1B0D3D;
margin-bottom: 8px;
.pi-info-circle {
font-size: 12px;
cursor: pointer;
}
}
& &__slippage {
margin-bottom: 8px;
}
& &__slippage-input {
width: 100%;
margin-bottom: 16px;
}
& &__transaction {
}
}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SettingsComponent } from './settings.component';
describe('SettingsComponent', () => {
let component: SettingsComponent;
let fixture: ComponentFixture<SettingsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ SettingsComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(SettingsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
@Component({
selector: 'os-settings',
templateUrl: './settings.component.html',
styleUrls: ['./settings.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SettingsComponent {
public slippageTolerance: any[] = [
{
label: '0.5%',
value: 0,
},
{
label: '1.0%',
value: 1,
},
{
label: '2.0%',
value: 2,
},
];
public transactionSpeed: any[] = [
{
label: 'Standard (5)',
value: 0,
},
{
label: 'Fast (6)',
value: 1,
},
{
label: 'Instant (7)',
value: 2,
},
];
public customSlippageTolerance: number = 0;
public selectedSlippageTolerance: number = 0;
public selectedTransactionSpeed: number = 0;
constructor(
private readonly _dynamicDialogRef: DynamicDialogRef,
private readonly _dynamicDialogConfig: DynamicDialogConfig,
) {
}
}
<div class="token-panel">
<div class="token-panel__header">
<span class="token-panel__label">{{ label }}</span>
<span class="token-panel__label">
{{ hint }}&nbsp;{{ token?.balance | normalizeBN }}
</span>
</div>
<div *ngIf="showCommitments"
(click)="onShowCommitmentsClick()"
class="token-panel__manual">
Switch to manual selection
<!-- Switch to automatic selection -->
</div>
<div class="token-panel__content">
<input #inputElement
[placeholder]="placeholder"
[disabled]="disabled"
[ngModel]="value"
[decimal]="true"
[max]="token?.balance | normalizeBN"
(ngModelChange)="onChange($event)"
digitOnly
type="text"
class="token-panel__input">
<span *ngIf="max > 0"
(click)="onMaxClick()"
class="token-panel__max">MAX</span>
<img [src]="token?.icon"
[alt]="token?.name"
class="token-panel__image">
<span class="token-panel__name">{{ token?.zkSymbol }}</span>
<span *ngIf="showAssets"
(click)="onShowAssetsClick()" class="pi pi-angle-down"></span>
</div>
</div>
@import "~src/styles/variables";
:host {
display: block;
}
.token-panel {
padding: 16px 20px;
background: rgba(255, 255, 255, 0.05);
border-radius: 3px;
display: flex;
flex-direction: column;
& &__header {
display: flex;
align-items: center;
justify-content: space-between;
}
& &__label {
font-weight: 600;
font-size: 12px;
line-height: 19px;
margin-bottom: 12px;
}
& &__content {
height: 36px;
display: flex;
align-items: center;
gap: 12px;
> .pi.pi-angle-down {
color: #AA8CFF;
font-size: 18px;
&:hover {
cursor: pointer;
}
}
}
& &__manual {
font-weight: 600;
font-size: 12px;
line-height: 19px;
color: #AA8CFF;
margin-bottom: 12px;
margin-left: auto;
margin-top: -8px;
&:hover {
cursor: pointer;
}
}
& &__input {
font-family: $offshift-font-family;
font-weight: 600;
font-size: 16px;
color: #FFFFFF;
width: 100%;
height: 100%;
border: 0;
background-color: transparent;
&::placeholder {
font-family: inherit;
font-weight: normal;
font-size: 14px;
color: #FFFFFF;
opacity: 0.5;
}
}
& &__max,
& &__name {
font-weight: 600;
font-size: 16px;
}
& &__max {
color: #AA8CFF;
&:hover {
cursor: pointer;
}
}
& &__name {
color: #FFFFFF;
}
& &__image {
width: 36px;
height: 36px;
display: block;
}
}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TokenPanelComponent } from './token-panel.component';
describe('SettingsComponent', () => {
let component: TokenPanelComponent;
let fixture: ComponentFixture<TokenPanelComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ TokenPanelComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(TokenPanelComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import {
Component,
ChangeDetectionStrategy,
Input,
EventEmitter,
Output,
forwardRef,
ChangeDetectorRef,
ViewChild,
ElementRef,
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { BigNumber } from 'bignumber.js';
import { TokensName } from '../../../../core/enums';
import { TokenType } from '../../../../core/types';
export const INPUT_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TokenPanelComponent),
multi: true,
};
@Component({
selector: 'os-token-panel',
templateUrl: './token-panel.component.html',
styleUrls: ['./token-panel.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [INPUT_VALUE_ACCESSOR],
})
export class TokenPanelComponent implements ControlValueAccessor {
@ViewChild('inputElement', { static: true })
public inputElement!: ElementRef<HTMLInputElement>;
@Input()
public label!: string;
@Input()
public placeholder!: string;
@Input()
public hint!: string;
@Input()
public max!: number;
@Input()
public showCommitments: boolean = false;
@Input()
public showAssets: boolean = false;
@Input()
public token!: TokenType;
@Output('showCommitmentsChange')
public commitmentsEventEmitter: EventEmitter<void> = new EventEmitter<void>();
@Output('showAssetsChange')
public assetsEventEmitter: EventEmitter<void> = new EventEmitter<void>();
public value!: string;
public disabled: boolean = false;
public get isNotXFT(): boolean {
return this.token?.symbol !== TokensName.XFT;
}
constructor(
private readonly _cdr: ChangeDetectorRef,
) {
}
public writeValue(value: string): void {
this.value = !isNaN(Number(value)) ? value : '';
this._cdr.markForCheck();
}
public registerOnChange(fn: () => void): void {
this._onChange = fn;
}
public registerOnTouched(fn: () => void): void {
this._onTouched = fn;
}
public setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
public onChange(value: string | number): void {
this.value = String(this.max && new BigNumber(value).gt(this.max) ? this.max : value);
if (this.inputElement?.nativeElement) {
const inputElement = this.inputElement.nativeElement as HTMLInputElement;
inputElement.value = this.value ? this.value : '';
}
this._onChange(this.value);
this._cdr.markForCheck();
}
public onMaxClick(): void {
this.onChange(this.max);
}
public onShowCommitmentsClick(): void {
this.commitmentsEventEmitter.emit();
}
public onShowAssetsClick(): void {
this.assetsEventEmitter.emit();
}
private _onChange: (value: string) => void = () => {
};
private _onTouched: () => void = () => {
};
}
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { SwapComponent } from './swap.component';
const ROUTES: Routes = [
{ path: '', component: SwapComponent },
];
@NgModule({
imports: [RouterModule.forChild(ROUTES)],
exports: [RouterModule]
})
export class SwapRoutingModule {
}
<div class="shift">
<div class="shift__header">
<h1 class="shift__header-text">Shift</h1>
<span (click)="onShowSettingsClick()"
class="shift__header-cog pi pi-cog"></span>
</div>
<div [formGroup]="formGroup"
class="shift__content">
<ng-template #tokenPanel
let-token="token"
let-name="name">
<os-token-panel [token]="token"
[showCommitments]="name === 'burn' && token?.symbol !== 'XFT'"
[showAssets]="token?.symbol !== 'XFT'"
[hint]="token?.symbol !== 'XFT' ? 'Total Balance:' : 'Balance:'"
[formControlName]="name"
[label]="name === 'burn' ? 'Burn' : 'Mint'"
(showCommitmentsChange)="onShowCommitmentsClick()"
(showAssetsChange)="onShowAssetsClick()"
placeholder="Amount">
</os-token-panel>
</ng-template>
<ng-container [ngTemplateOutlet]="tokenPanel"
[ngTemplateOutletContext]="{ token: tokenInput, name: 'burn' }">
</ng-container>
<button (click)="onSwitchTokensClick(tokenInput.symbol, tokenOutput.symbol)"
class="shift__switch p-button-rounded p-button-icon-only"
pButton
type="button"
icon="pi pi-sort-alt">
</button>
<ng-container [ngTemplateOutlet]="tokenPanel"
[ngTemplateOutletContext]="{ token: tokenOutput, name: 'mint' }">
</ng-container>
</div>
<div class="shift__footer">
<ng-container *ngIf="(accountAddress$ | ngrxPush) else connectWallet">
<ng-container [ngSwitch]="buttonState">
<button *ngSwitchCase="'APPROVE'"
[disabled]="formGroup.disabled || formGroup.get('burn').value <= 0"
(click)="onApproveClick()"
pButton
type="button"
label="Approve"
class="shift__button">
</button>
<button *ngSwitchCase="'APPROVING'"
[disabled]="true"
[loading]="true"
pButton
type="button"
label="Approving..."
class="shift__button">
</button>
<button *ngSwitchCase="'SHIFT'"
[disabled]="formGroup.disabled"
[loading]="formGroup.disabled"
(click)="onShiftClick()"
pButton
type="button"
label="Shift"
class="shift__button">
</button>
</ng-container>
</ng-container>
<ng-template #connectWallet>
<os-connect-wallet class="shift__button"></os-connect-wallet>
</ng-template>
</div>
</div>
@use "sass:math";
@import "~src/styles/variables";
:host {
display: flex;
flex-direction: column;
}
.shift {
display: flex;
flex-direction: column;
margin: 84px auto 0;
padding: 32px 24px;
width: 411px;
bottom: 173px;
background: #1B0D3D;
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.32);
border-radius: 8px;
gap: math.div($offshift-gap * 3, 2);;
& &__header {
display: flex;
align-items: center;
justify-content: space-between;
}
& &__header-text {
font-weight: 600;
font-size: 20px;
line-height: 32px;
margin: 0;
}
& &__header-cog {
font-size: 18px;
cursor: pointer;
}
& &__content {
height: 100%;
display: flex;
flex-direction: column;
gap: $offshift-gap;
}
& &__switch {
margin: 0 auto;
}
& &__footer {
}
& &__button {
display: block;
width: 100%;
}
}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SwapComponent } from './swap.component';
describe('SwapComponent', () => {
let component: SwapComponent;
let fixture: ComponentFixture<SwapComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [SwapComponent]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(SwapComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { DialogService } from 'primeng/dynamicdialog';
import {
combineLatest,
debounceTime,
distinctUntilChanged,
filter,
first,
firstValueFrom, map,
mergeMap,
Observable
} from 'rxjs';
import { BaseComponent } from '../../base.component';
import { GetAllowanceRequest, GetBalancesRequest, GetBalancesSuccess, GetXFTBalanceRequest } from '../../core/actions';
import { TOKENS_AGGREGATOR_MAP, TOKENS_NAME_MAP, TokensName } from '../../core/enums';
import { normalize, TOKENS_MAP, toWei, valueToBigNumber, valueToWei } from '../../core/helpers';
import {
selectAccountAddress,
selectAllowance,
selectBalances,
selectTokenBySymbol,
selectXFTBalance,
} from '../../core/selectors';
import { ContractService } from '../../core/services/contract.service';
import { TokenType } from '../../core/types';
import { AssetsComponent } from './components/assets/assets.component';
import { CommitmentsComponent } from './components/commitments/commitments.component';
import { SettingsComponent } from './components/settings/settings.component';
import { BalanceCommitment } from '../../core/interfaces/contract-service-methods';
import { concatLatestFrom } from '@ngrx/effects';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'os-swap',
templateUrl: './swap.component.html',
styleUrls: ['./swap.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SwapComponent extends BaseComponent implements OnInit {
public formGroup: FormGroup;
public isBurnXFT: boolean = false;
public buttonState: 'APPROVE' | 'APPROVING' | 'SHIFT' = 'APPROVE';
public tokenInput: TokenType;
public tokenOutput: TokenType;
public accountAddress$: Observable<string>;
public tokenBalance$: Observable<string>;
private _allowance: string;
private _selectedCommitment: BalanceCommitment;
private _burnAsset: { price: string; decimals: number };
private _mintAsset: { price: string; decimals: number };
constructor(
store: Store,
private readonly _route: ActivatedRoute,
private readonly _cdr: ChangeDetectorRef,
private readonly _formBuilder: FormBuilder,
private readonly _dialogService: DialogService,
private readonly _contractService: ContractService,
) {
super(store);
this.accountAddress$ = this.store.select(selectAccountAddress);
}
public async ngOnInit(): Promise<void> {
this.formGroup = this._formBuilder.group({
burn: [null, [Validators.required]],
mint: [null, [Validators.required]],
});
this.onSwitchTokensClick(TokensName.BTC, TokensName.XFT);
combineLatest([
this.store.select(selectBalances),
this.store.select(selectXFTBalance),
])
.subscribe(() => {
this.tokenInput = { ...TOKENS_MAP[this.tokenInput.symbol] };
this.tokenOutput = { ...TOKENS_MAP[this.tokenOutput.symbol] };
this._cdr.markForCheck();
});
this.store.select(selectAccountAddress)
.pipe(
filter((accountAddress) => Boolean(accountAddress)),
)
.subscribe(async () => {
await this._getLatestPrice();
});
this.store.select(selectAllowance)
.subscribe((allowance) => {
this._allowance = allowance;
const burn = this.formGroup.get('burn').value;
this._toggleButtonState(burn);
});
this.formGroup.get('burn').valueChanges
.pipe(
debounceTime(500),
distinctUntilChanged(),
filter(() => this.formGroup.enabled),
)
.subscribe(async (burn) => {
this._toggleButtonState(burn);
const aggrBase = TOKENS_NAME_MAP[this.tokenInput.symbol];
const aggrQuote = TOKENS_NAME_MAP[this.tokenOutput.symbol];
const mint = await this._getExchange(burn, aggrBase, aggrQuote);
this.formGroup.get('mint').setValue(mint, { emitEvent: false });
});
this.formGroup.get('mint').valueChanges
.pipe(
debounceTime(500),
distinctUntilChanged(),
filter(() => this.formGroup.enabled),
)
.subscribe(async (mint) => {
const aggrBase = TOKENS_NAME_MAP[this.tokenOutput.symbol];
const aggrQuote = TOKENS_NAME_MAP[this.tokenInput.symbol];
const burn = await this._getExchange(mint, aggrBase, aggrQuote);
this.formGroup.get('burn').setValue(burn, { emitEvent: false });
});
this._route.queryParams
.pipe(
filter(({ symbol, commitmentId }) => Boolean(symbol && commitmentId)),
concatLatestFrom(() => this.accountAddress$),
mergeMap(([{ symbol, commitmentId }, address]) => {
this.onSwitchTokensClick(TokensName.XFT, symbol);
return this._contractService.balances({ address })
.pipe(
map((balances) => {
this.store.dispatch(GetBalancesSuccess({ balances }));
const balance = balances.find((b) => b.asset.symbol === symbol);
return balance?.commitments.find(({ id }) => id == commitmentId);
}),
);
})
)
.subscribe((commitment) => this._setCommitment(commitment));
}
public async onApproveClick(): Promise<void> {
try {
this.formGroup.disable();
this.buttonState = 'APPROVING';
let amount = '0';
if (this._selectedCommitment) {
amount = this._selectedCommitment.amount;
} else {
const { burn } = this.formGroup.getRawValue();
amount = valueToWei(burn, 18);
}
await this._contractService.approve(amount, (error: any, hash: string) => {
console.log(error);
console.log(hash);
});
this.buttonState = 'SHIFT';
this.formGroup.enable();
this.store.dispatch(GetAllowanceRequest());
} catch (error) {
this.formGroup.enable();
this.buttonState = 'APPROVE';
console.log(error);
}
this._cdr.markForCheck();
}
public async onShiftClick(): Promise<void> {
try {
this.formGroup.disable();
const address = await firstValueFrom(this.accountAddress$);
const { burn, mint } = this.formGroup.getRawValue();
const callback = (error: any, hash: string) => {
console.log(error);
console.log(hash);
};
if (this.tokenOutput.symbol === TokensName.XFT) {
const { id, amount } = this._selectedCommitment; // TODO remove hard code
const { transactionHash: hashTx } = await this._contractService
.withdraw(address, amount, id, callback);
await this._contractService.withdrawFill({ address, hashTx });
} else {
const amount = valueToWei(mint, 18);
const symbol = TOKENS_NAME_MAP[this.tokenOutput.symbol];
const { transactionHash: hashTx } = await this._contractService
.deposit(address, amount, symbol, callback);
await this._contractService.depositFill({ address, hashTx });
}
this.store.dispatch(GetBalancesRequest());
this.store.dispatch(GetXFTBalanceRequest());
this.store.dispatch(GetAllowanceRequest());
this.buttonState = 'APPROVE';
this._selectedCommitment = undefined;
this.formGroup.reset({ burn: null, mint: null });
} catch (error) {
console.log(error);
}
this.formGroup.enable();
this.formGroup.updateValueAndValidity();
this._cdr.markForCheck();
}
public onSwitchTokensClick(
tokenInput: TokensName,
tokenOutput: TokensName,
): void {
this.isBurnXFT = !this.isBurnXFT;
this.tokenInput = { ...TOKENS_MAP[tokenOutput] };
this.tokenOutput = { ...TOKENS_MAP[tokenInput] };
const { burn, mint } = this.formGroup.getRawValue();
if (this.isBurnXFT) {
this.formGroup.get('burn').setValue(burn);
}
this._cdr.markForCheck();
}
public onShowSettingsClick(): void {
this._dialogService.open(SettingsComponent, {
header: 'Settings',
width: '500px',
});
}
public onShowAssetsClick(): void {
const dialog = this._dialogService.open(AssetsComponent, {
header: 'Asset Search',
width: '500px',
});
dialog.onClose.subscribe(async (selectedAsset?: TokenType) => {
if (!selectedAsset) {
return;
}
const { burn } = this.formGroup.getRawValue();
if (this.isBurnXFT) {
this.tokenOutput = { ...TOKENS_MAP[selectedAsset.symbol] };
} else {
this.tokenInput = { ...TOKENS_MAP[selectedAsset.symbol] };
}
const aggrBase = TOKENS_NAME_MAP[this.tokenInput.symbol];
const aggrQuote = TOKENS_NAME_MAP[this.tokenOutput.symbol];
const mint = await this._getExchange(burn, aggrBase, aggrQuote);
this.formGroup.get('mint').setValue(mint, { emitEvent: false });
this._cdr.markForCheck();
});
}
public onShowCommitmentsClick(): void {
this.store.select(selectTokenBySymbol(this.tokenInput.symbol))
.pipe(
first(),
mergeMap((token) => {
return this._dialogService.open(CommitmentsComponent, {
header: 'Select Commitments',
width: '500px',
data: {
token,
selectedCommitment: this._selectedCommitment,
},
}).onClose;
}),
filter((commitment) => !!commitment),
)
.subscribe((commitment) => {
this._setCommitment(commitment);
});
}
private _setCommitment(commitment: BalanceCommitment): void {
this._selectedCommitment = commitment;
const burn = normalize(this._selectedCommitment.amount, 18);
this.formGroup.get('burn').setValue(burn);
}
private async _getExchange(
amount: string,
aggrBase: TokensName,
aggrQuote: TokensName,
): Promise<number> {
if (!amount) {
return null;
}
const { Price, Decimal } = await this._contractService.exchange({
aggrBase,
aggrQuote,
amount: valueToWei(amount, 18),
}).toPromise();
const result = Number(normalize(Price, Decimal));
return result > 0 ? result : null;
}
private _toggleButtonState(amount: string): void {
const isLTE = amount && valueToBigNumber(toWei(amount)).lte(this._allowance);
this.buttonState = isLTE ? 'SHIFT' : 'APPROVE';
this._cdr.markForCheck();
}
private async _getLatestPrice(): Promise<void> {
const burnAggregator = TOKENS_AGGREGATOR_MAP[this.tokenInput.symbol];
const mintAggregator = TOKENS_AGGREGATOR_MAP[this.tokenOutput.symbol];
this._burnAsset = await this._contractService.getLatestPrice(burnAggregator);
this._mintAsset = await this._contractService.getLatestPrice(mintAggregator);
}
}
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ReactiveComponentModule } from '@ngrx/component';
import { DigitOnlyModule } from '@uiowa/digit-only';
import { ButtonModule } from 'primeng/button';
import { InputNumberModule } from 'primeng/inputnumber';
import { InputTextModule } from 'primeng/inputtext';
import { SelectButtonModule } from 'primeng/selectbutton';
import { TableModule } from 'primeng/table';
import { SimplebarAngularModule } from 'simplebar-angular';
import { ConnectWalletModule } from '../../components/connect-wallet/connect-wallet.module';
import { PipesModule } from '../../core/pipes';
import { TokenPanelComponent } from './components/token-panel/token-panel.component';
import { SwapRoutingModule } from './swap-routing.module';
import { SwapComponent } from './swap.component';
import { SettingsComponent } from './components/settings/settings.component';
import { AssetsComponent } from './components/assets/assets.component';
import { CommitmentsComponent } from './components/commitments/commitments.component';
@NgModule({
declarations: [
AssetsComponent,
CommitmentsComponent,
SettingsComponent,
SwapComponent,
TokenPanelComponent,
],
imports: [
ButtonModule,
CommonModule,
FormsModule,
InputNumberModule,
InputTextModule,
SelectButtonModule,
SimplebarAngularModule,
SwapRoutingModule,
TableModule,
ConnectWalletModule,
ReactiveComponentModule,
PipesModule,
ReactiveFormsModule,
DigitOnlyModule,
],
})
export class SwapModule {
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment