Dappsの開発

概要

Dapps開発のフローをご紹介します。

今回作成するDappsはオーナーが作成した本棚の本を、ゲストがトークンを支払うことで閲覧することが出来る電子図書館をイメージしたプロダクトです。

概念モデル設計

  • ユーザーにはオーナー(貸主)とゲスト(借主)がいる
  • オーナーは依託金を支払うことで本棚を作成することができる
  • 本棚を作成すると、オーナーは所有する本のデータを保存することができる
  • ゲストはオーナーが設定した額のトークを支払うことで、本を閲覧することができる
  • 期間が過ぎると依託金と支払われたトークンがオーナーに送られる

概念データモデル

f:id:adrenaline2017:20190707102533j:plain

ユーザーエンティティ

  • Ethereumアカウントアドレス
  • パスワード
  • 報酬額

シェルフエンティティ

  • ユーザーID
  • コントラクトアドレス
  • シャルフ名
  • トークン残高
  • シェルフの期限
  • アクティブ状態

ブックエンティティ

  • ブックID
  • ブックに紐付くシェルフID
  • 閲覧者ID
  • 閲覧者のアドレス

f:id:adrenaline2017:20190911112610j:plain

トランザクション

シェルフの作成

  • トランザクションハッシュ
  • 送金額
  • 送信者アドレス
  • 受信者アドレス
  • 成功の可否
  • シェルの作成者ID
  • シェルフID
  • 作成時刻

預託

  • トランザクションハッシュ
  • 送信者アドレス
  • 受信者アドレス
  • 送金額
  • 成功の可否
  • 承認の可否
  • 預託者ID
  • 預託先シェルフID
  • 預託時刻

報酬の支払

  • トランザクションハッシュ
  • 送信者アドレス
  • 受信者アドレス
  • 送金額
  • 成功の可否
  • 承認の可否
  • ブックID
  • 支払い先シェルフID
  • 支払時刻

返金

  • トランザクションハッシュ
  • 送金者アドレス
  • 受信者アドレス
  • 送金額
  • 成功の可否
  • 承認の可否
  • シェルフID
  • 返金先のオーナーID
  • 返金時刻

本の追加

  • オーナーID
  • ブックID
  • 題名
  • 価格
  • 詳細
  • 追加時刻

論理データモデル

f:id:adrenaline2017:20190707102554j:plain

物理データモデルの設計

userテーブル

import {Entity, PrimaryGeneratedColumn, Column, Index, CreateDateColumn, UpdateDateColumn, OneToMany} from "typeorm";
import { Shelf } from "./Shelf";

@Entity()
export class User {

    @PrimaryGeneratedColumn({
        type: 'bigint',
        unsigned: true,
        comment: 'user id',
    })
    readonly id: string;

    @Index('eth_address_UNIQUE', {unique: true})
    @Column('varchar',{
        length: '255',
        nullable: false,
        comment: 'ethereum address',
    })
    eth_address: string;

    @Column('varchar',{
        length: '255',
        default: null,
        comment: 'password',
    })
    encrypted_password: string | null = null; 

    @Column('varchar',{
        length: '255',
        default: null,
        comment: 'solt',
    })
    salt: string | null = null;

    @Column('bigint', {
        unsigned: true,
        nullable: false,
        default: 0,
        comment: 'reword amount',
    })
    reword_amount: string;

    @CreateDateColumn({
        name: 'created_at',
        type: 'timestamp',
        default: () => 'CURRENT_TIMESTAMP',
        precision: 0,
        comment: 'created date',
      })
      readonly createdAt?: Date;
    
      @UpdateDateColumn({
        name: 'updated_at',
        type: 'timestamp',
        default: () => 'CURRENT_TIMESTAMP',
        precision: 0,
        comment: 'update date',
      })
      readonly updatedAt?: Date;

      @OneToMany((type) => Shelf, (shelf) => shelf.user)
      readonly shelfs?: Shelf[];
}

shelfテーブル

import { Entity, PrimaryGeneratedColumn, Index, Column, ManyToOne, JoinColumn, OneToMany } from "typeorm";
import { User } from "./User";
import { Book } from "./Book";

@Entity()
export class Shelf {

    @PrimaryGeneratedColumn({
        type: 'bigint',
        unsigned: true,
        comment: 'shelf id',
    })
    readonly id: string;

    @Index('event_code_UNIQUE', {unique: true})
    @Column('varchar', {
        length: '255',
        default: null,
        comment: 'event code'
    })
    event_code: string | null = null;

    @Column('bigint', {
        name: 'user_id',
        unsigned: true,
        nullable: false,
        comment: 'user id',
    })
    user_id: string

    @Column('varchar', {
        length: '255',
        default: null,
        comment: 'owner address',
    })
    owner_address: string | null = null;
    
    @Column('varchar', {
        length: '255',
        default: null,
        comment: 'transaction hash',
    })
    create_tx_hash: string | null = null;

    @Column('bigint', {
        unsigned: true,
        default: 0,
        comment: 'amount(wei)'
    })
    wei_balance: string;

    @Column('bigint', {
        default: null,
        comment: 'start date',
    })
    start_date: Date | null = null;

    @Column('bigint', {
        default: null,
        comment: 'end date',
    })
    end_date: Date | null = null;

    @ManyToOne((type) => User, (user) => user.shelfs)
    @JoinColumn({name: 'user_id', referencedColumnName: 'id'})
    readonly user?: User;

    @OneToMany((type) => Book, (book) => book.books)
      readonly books?: Book[];
}

bookテーブル

import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn } from 'typeorm';
import { Shelf } from './Shelf';

@Entity()
export class Book {

    @PrimaryGeneratedColumn({
        type: 'bigint',
        unsigned: true,
        comment: 'book id',
    })
    public id: string;

    @Column('bigint', {
        name: 'shelf_id',
        unsigned: true,
        nullable: false,
        comment: 'shelf id',
    })
    shelf_id: string;

    @Column('bigint', {
        unsigned: true,
        default: null,
        comment: 'borrower id'
    })
    borrower_id: string | null = null;

    @Column('varchar',{
        length: '255',
        nullable: false,
        comment: 'borrower address',
    })
    borrower_address: string;

    @CreateDateColumn({
        name: 'created_at',
        type: 'timestamp',
        default: () => 'CURRENT_TIMESTAMP',
        precision: 0,
        comment: 'created date',
      })
      readonly createdAt?: Date;
    
    @UpdateDateColumn({
        name: 'updated_at',
        type: 'timestamp',
        default: () => 'CURRENT_TIMESTAMP',
        precision: 0,
        comment: 'updated date',
    })
    readonly updatedAt?: Date;

    @ManyToOne((type) => Shelf, (shelf) => shelf.books)
    @JoinColumn({name: 'shelf_id', referencedColumnName: 'id'})
    readonly books?: Shelf[];
}

Paymantテーブル

import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from "typeorm";

@Entity()
export class Payment {

    @PrimaryGeneratedColumn({
        type: 'bigint',
        unsigned: true,
        comment: 'deposit id',
    })
    readonly id?: string;
    
    @Column('varchar',{
        length: '255',
        default: null,
        comment: 'transaction hash',
    })
    tx_hash: string | null = null;

    @Column('varchar',{
        length: '255',
        nullable: false,
        default: '0x0',
        comment: 'sender',
    })
    sender: string;

    @Column('varchar',{
        length: '255',
        nullable: false,
        default: '0x0',
        comment: 'receiver',
    })
    receiver: string;

    @Column('bigint',{
        unsigned: true,
        default: 0,
        comment: 'amount(wei)',
    })
    wei_amount: string;

    @Column('tinyint',{
        default: 0,
        comment: 'success flag',
    })
    success: boolean;

    @Column('tinyint',{
        default: 0,
        comment: 'confirmed flag',
    })
    confirmed: boolean;

    @Column('bigint', {
        unsigned: true,
        nullable: false,
        comment: 'shelf id',
    })
    shelf_id: string;

    @CreateDateColumn({
        name: 'created_at',
        type: 'timestamp',
        default: () => 'CURRENT_TIMESTAMP',
        precision: 0,
        comment: '作成日',
      })
      readonly createdAt?: Date;
    
    @UpdateDateColumn({
        name: 'updated_at',
        type: 'timestamp',
        default: () => 'CURRENT_TIMESTAMP',
        precision: 0,
        comment: '更新日',
    })
    readonly updatedAt?: Date;
}

Depositテーブル

import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from "typeorm";

@Entity()
export class Deposit {

    @PrimaryGeneratedColumn({
        type: 'bigint',
        unsigned: true,
        comment: 'deposit id',
    })
    readonly id?: string;
    
    @Column('varchar',{
        length: '255',
        default: null,
        comment: 'transaction hash',
    })
    tx_hash: string | null = null;

    @Column('varchar',{
        length: '255',
        nullable: false,
        default: '0x0',
        comment: 'sender',
    })
    sender: string;

    @Column('varchar',{
        length: '255',
        nullable: false,
        default: '0x0',
        comment: 'receiver',
    })
    receiver: string;

    @Column('bigint',{
        unsigned: true,
        default: 0,
        comment: 'amount(wei)',
    })
    wei_amount: string;

    @Column('tinyint',{
        default: 0,
        comment: 'success flag',
    })
    success: boolean;

    @Column('tinyint',{
        default: 0,
        comment: 'confirmed flag',
    })
    confirmed: boolean;

    @Column('bigint', {
        unsigned: true,
        nullable: false,
        comment: 'shelf id',
    })
    shelf_id: string;

    @CreateDateColumn({
        name: 'created_at',
        type: 'timestamp',
        default: () => 'CURRENT_TIMESTAMP',
        precision: 0,
        comment: '作成日',
      })
      readonly createdAt?: Date;
    
    @UpdateDateColumn({
        name: 'updated_at',
        type: 'timestamp',
        default: () => 'CURRENT_TIMESTAMP',
        precision: 0,
        comment: '更新日',
    })
    readonly updatedAt?: Date;
}

スマートコントラクトの設計

機能の洗い出し

ShelfFactoryコントラクトとShelfコントラクトの各機能を洗い出す。

  • ShelfFactoryコントラクト 本棚を作成する機能

  • Shelfコントラクト 本棚への預託機能 オーナーへの返金機能 本棚の活性化・非活性化機能