diff --git a/README.md b/README.md index 9c57f8f3037df618278c14b1b8bde7e3f30b7452..ec6fae28f04c6d75c33c7c43fcb192338169cba4 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ # cesium2s -Cesium 2, running on Substrate \ No newline at end of file +Cesium 2, running on Substrate + + +npm install -g yarn @ionic/cli @angular/cli diff --git a/src/app/services/base.service.ts b/src/app/services/base.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..2a6d6b7882a8480990f9c671b54023a2ef8d85b2 --- /dev/null +++ b/src/app/services/base.service.ts @@ -0,0 +1,76 @@ +import {Injectable} from "@angular/core"; +import {Platform} from "@ionic/angular"; +import {environment} from "../../environments/environment"; + +@Injectable() +export abstract class AppBaseService { + + private readonly _debug: boolean; + private readonly _logPrefix: string = null; + + private _started = false; + private _startPromise: Promise<void> = null; + + + get started(): boolean { + return this._started; + } + + protected constructor(protected platform: Platform, opts?: { + logPrefix?: string; + name?: string; + }) { + this._debug = !environment.production; + this._logPrefix = (opts && opts.logPrefix) || ("[" + (opts && opts.name || 'base-service') + "] "); + } + + start(): Promise<any> { + if (this._startPromise) return this._startPromise; + if (this._started) return Promise.resolve(); + + this._started = false; + const now = Date.now(); + this.info('Starting service...'); + + this._startPromise = this.platform.ready() + .then(() => this.doStart()) + .then((result: any) => { + this._started = true; + this._startPromise = undefined; + this.info(`Starting service [OK] in ${Date.now() - now}ms`); + return result; + }) + .catch((err) => { + this.error('Cannot start:', err); + throw err; // Rethrow + }); + + return this._startPromise; + } + + ready(): Promise<boolean> { + if (this._started) return Promise.resolve(true); + return this.start() + .then((_) => { + return true; + }) + .catch((err) => { + return false; + }); + } + + protected abstract doStart(): Promise<any>; + + protected debug(msg, ...params: any[]) { + if (this._debug) console.debug(this._logPrefix + msg, params); + } + protected info(msg, ...params: any[]) { + console.info(this._logPrefix + msg, params); + } + protected warn(msg, ...params: any[]) { + console.warn(this._logPrefix + msg, params); + } + protected error(msg, ...params: any[]) { + console.error(this._logPrefix + msg, params); + } +} diff --git a/src/app/services/node.service.ts b/src/app/services/node.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..6a084f23a06414e630544e16aa0036b32a353eb9 --- /dev/null +++ b/src/app/services/node.service.ts @@ -0,0 +1,35 @@ +import {Injectable} from "@angular/core"; +import {Platform} from "@ionic/angular"; +import {AppBaseService} from "./base.service"; +import {ApiPromise, WsProvider} from "@polkadot/api"; +import {web3Accounts, web3Enable, web3FromAddress} from "@polkadot/extension-dapp"; +import {environment} from "../../environments/environment"; + +@Injectable({providedIn: 'root'}) +export class NodeService extends AppBaseService { + + private _api: ApiPromise; + + get api(): ApiPromise { + return this._api + } + + constructor( + platform: Platform + ) { + super(platform, { + name: 'node-service' + }); + } + + protected async doStart(): Promise<any> { + // Construct + const wsProvider = new WsProvider('ws://localhost:9944'); + const api = await ApiPromise.create({ provider: wsProvider }); + + // Do something + this.info("Connected to Blockchain genesis: " + api.genesisHash.toHex()); + + this._api = api; + } +} diff --git a/src/app/services/platform.service.ts b/src/app/services/platform.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..d9e882ae862624b300bfb8c0c7dfa40c535b88d3 --- /dev/null +++ b/src/app/services/platform.service.ts @@ -0,0 +1,43 @@ +import {Injectable} from "@angular/core"; +import {Platform} from "@ionic/angular"; +import {AppBaseService} from "./base.service"; +import {NodeService} from "./node.service"; + +@Injectable({providedIn: 'root'}) +export class PlatformService extends AppBaseService { + + private _mobile: boolean = null; + private _touchUi: boolean = null; + + + get mobile(): boolean { + return this._mobile != null ? this._mobile : this.platform.is('mobile'); + } + + get touchUi(): boolean { + return this._touchUi != null ? this._touchUi : + (this.mobile || this.platform.is('tablet') || this.platform.is('phablet')); + } + + constructor( + platform: Platform, + private node: NodeService + ) { + super(platform, { + name: 'platform-service' + }) + this.start(); + } + + async doStart(): Promise<any> { + + this._mobile = this.mobile; + this._touchUi = this.touchUi; + + await Promise.all([ + this.node.start() + ] + ) + // TODO: Init required service + } +} diff --git a/src/app/services/wallet.service.ts b/src/app/services/wallet.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..1faa6504242effbdc9df1169323391c944c39ff3 --- /dev/null +++ b/src/app/services/wallet.service.ts @@ -0,0 +1,82 @@ +import {Injectable} from "@angular/core"; +import {Platform} from "@ionic/angular"; +import {AppBaseService} from "./base.service"; +import {web3Accounts, web3Enable, web3FromAddress, web3FromSource} from "@polkadot/extension-dapp"; +import {environment} from "../../environments/environment"; +import {NodeService} from "./node.service"; +import {ApiPromise} from "@polkadot/api"; +import {InjectedAccountWithMeta} from "@polkadot/extension-inject/types"; + +@Injectable({providedIn: 'root'}) +export class WalletService extends AppBaseService { + + accounts: InjectedAccountWithMeta[]; + + get api(): ApiPromise { + return this.node.api; + } + + constructor( + platform: Platform, + protected node: NodeService + ) { + super(platform, { + name: 'wallet-service' + }) + } + + protected async doStart(): Promise<any> { + + await this.node.ready(); + + // returns an array of all the injected sources + // (this needs to be called first, before other requests) + const extensions = await web3Enable(environment.name); + if (extensions.length === 0) { + // no extension installed, or the user did not accept the authorization + // in this case we should inform the use and give a link to the extension + console.debug('No web3 extension found'); + } + + // returns an array of { address, meta: { name, source } } + // meta.source contains the name of the extension that provides this account + this.accounts = await web3Accounts(); + + + if (this.accounts?.length === 0) { + this.accounts = [{ + address: '5ESoncgJ42j8WAyh9SBk2ztMZhUzejEZxF93LnZVBCXR77Kg', + meta: { + name: 'Alice', + source: '0xc74be00087825d9f8ba924d38fde43a8e8953c40c8f6a269b8a4ca337fbef7b7' + } + }] + } + } + + async transfer() { + + // the address we use to use for signing, as injected + const SENDER = this.accounts[0].address; + + // finds an injector for an address + //const injector = await web3FromAddress(SENDER); + const injector = await web3FromSource(this.accounts[0].meta.source); + + // sign and send our transaction - notice here that the address of the account + // (as retrieved injected) is passed through as the param to the `signAndSend`, + // the API then calls the extension to present to the user and get it signed. + // Once complete, the api sends the tx + signature via the normal process + this.api.tx.balances + .transfer('5C5555yEXUcmEJ5kkcCMvdZjUo7NGJiQJMS7vZXEeoMhj3VQ', 12) + .signAndSend(SENDER, { signer: injector.signer }, (status) => { + if (status.isInBlock) { + console.log(`Completed at block hash #${status}`); + } else { + console.log(`Current status: ${status}`); + } + }).catch((error: any) => { + console.log(':( transaction failed', error); + }); + } +} diff --git a/src/app/wallet/wallet-routing.module.ts b/src/app/wallet/wallet-routing.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..d05d7da8fda09150268f3c00b05e8ea03148b966 --- /dev/null +++ b/src/app/wallet/wallet-routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { WalletPage } from './wallet.page'; + +const routes: Routes = [ + { + path: '', + component: WalletPage + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class WalletPageRoutingModule {} diff --git a/src/app/wallet/wallet.module.ts b/src/app/wallet/wallet.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..074075ed084d0978eebb723db1434d621d74f02d --- /dev/null +++ b/src/app/wallet/wallet.module.ts @@ -0,0 +1,19 @@ +import {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {FormsModule} from '@angular/forms'; + +import {IonicModule} from '@ionic/angular'; + +import {WalletPage} from './wallet.page'; +import {WalletPageRoutingModule} from "./wallet-routing.module"; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + IonicModule, + WalletPageRoutingModule + ], + declarations: [WalletPage] +}) +export class WalletPageModule {} diff --git a/src/app/wallet/wallet.page.html b/src/app/wallet/wallet.page.html new file mode 100644 index 0000000000000000000000000000000000000000..471b35f5fe328c4c76f2b1a6a979c8c3d2a2abf2 --- /dev/null +++ b/src/app/wallet/wallet.page.html @@ -0,0 +1,31 @@ +<ion-header [translucent]="true"> + <ion-toolbar> + <ion-buttons slot="start"> + <ion-menu-button></ion-menu-button> + </ion-buttons> + <ion-title>{{ walletId }}</ion-title> + </ion-toolbar> +</ion-header> + +<ion-content [fullscreen]="true"> + <ion-header collapse="condense"> + <ion-toolbar> + <ion-title size="large">{{ walletId }}</ion-title> + </ion-toolbar> + </ion-header> + + <div id="container"> + + <ion-list> + <ion-item> + <ion-icon slot="start" name="person"></ion-icon> + <ion-text>Test</ion-text> + </ion-item> + </ion-list> + + <ion-button (click)="wallet.transfer()"> + <ion-icon slot="start" name="paper-plane"></ion-icon> + <ion-label>Envoyer</ion-label> + </ion-button> + </div> +</ion-content> diff --git a/src/app/wallet/wallet.page.scss b/src/app/wallet/wallet.page.scss new file mode 100644 index 0000000000000000000000000000000000000000..281733f194d1a6cca00e20cad7302c7779ffd76b --- /dev/null +++ b/src/app/wallet/wallet.page.scss @@ -0,0 +1,6 @@ +ion-menu-button { + color: var(--ion-color-primary); +} + +#container { +} diff --git a/src/app/wallet/wallet.page.spec.ts b/src/app/wallet/wallet.page.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..f4b32923f41adcd8561e39f38f0c1e28e49506ba --- /dev/null +++ b/src/app/wallet/wallet.page.spec.ts @@ -0,0 +1,24 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { IonicModule } from '@ionic/angular'; +import { RouterModule } from '@angular/router'; +import { WalletPage } from './wallet.page'; + +describe('FolderPage', () => { + let component: WalletPage; + let fixture: ComponentFixture<WalletPage>; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ WalletPage ], + imports: [IonicModule.forRoot(), RouterModule.forRoot([])] + }).compileComponents(); + + fixture = TestBed.createComponent(WalletPage); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/wallet/wallet.page.ts b/src/app/wallet/wallet.page.ts new file mode 100644 index 0000000000000000000000000000000000000000..6bf5ac25f6215638f215fd36216172c77dd144ff --- /dev/null +++ b/src/app/wallet/wallet.page.ts @@ -0,0 +1,31 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import {WalletService} from "../services/wallet.service"; + +@Component({ + selector: 'app-wallet', + templateUrl: './wallet.page.html', + styleUrls: ['./wallet.page.scss'], +}) +export class WalletPage implements OnInit { + public walletId: string; + + constructor( + public wallet: WalletService, + private activatedRoute: ActivatedRoute + ) { + + this.load(); + } + + ngOnInit() { + this.walletId = this.activatedRoute.snapshot.paramMap.get('id'); + } + + async load() { + await this.wallet.ready(); + + + } + +} diff --git a/src/environments/environment.class.ts b/src/environments/environment.class.ts new file mode 100644 index 0000000000000000000000000000000000000000..ae86fa2d8843c58e9226e7f53fe8ce94898ef509 --- /dev/null +++ b/src/environments/environment.class.ts @@ -0,0 +1,5 @@ +export interface Environment { + name: string; + version?: string; + production: boolean; +}