Anglar,cosmos-client의 블록체인 웹 응용 프로그램 프런트엔드 개발2(추가 전송 기능)
88995 단어 AngularTypeScriptcosmostech
환경 구조
운영 환경
Angular Material
Anglar Material은 Anglar가 소재 디자인 기반 UI를 쉽게 설정할 수 있는 프레임워크입니다.
자세한 내용은 이곳의 정보를 참조하시오.
Anglar 공식 UI 구성 요소 사용 방법
Anglar Material을 가져오면 샘플 코드에 사용됩니다.
ng add @angular/material
이번엔 6개 모듈을 추가한다.app.module.ts
import { MatGridListModule } from '@angular/material/grid-list';//追加
import { MatCardModule } from '@angular/material/card';//追加
import { MatFormFieldModule } from '@angular/material/form-field';//追加
import { MatButtonModule } from '@angular/material/button';//追加
import { MatInputModule } from '@angular/material/input';//追加
import { MatSelectModule } from '@angular/material/select';//追加
imports: [
BrowserModule,
AppRoutingModule,
FormsModule,
BrowserAnimationsModule,
MatGridListModule, //追加
MatCardModule, //追加
MatFormFieldModule, //追加
MatButtonModule, //追加
MatInputModule, //追加
MatSelectModule //追加
],
TailWind CSS
TailWind CSS는 HTML에서 클래스만 설정하면 CSS와 접촉하지 않고 화면 디자인을 할 수 있는 프레임입니다.
TailWind CSS의 유효성방정식 문서의 예은 쉽게 이해할 수 있습니다.나는 기술량이 감소하고 있다는 것을 안다.
우리도 수입한다.
ng add @ngneat/tailwind
로컬 체인 노드의 수수료 설정
다음 경로에는 체인 노드 설정 파일이 있습니다.
파일을 찾을 수 없으면 폴더 이름입니다.마스의 부분을 시작된 체인의 이름으로 적당히 변경해 주세요.
텍스트 편집기는 견본에서 Vim을 사용합니다.익숙해진 물건을 사용하세요.
cd ~/.mars/config
vim app.toml
다음 미니멀-gas-prices에서gas로 사용되는 영패와 수량을 설정합니다.여기에는 1gas=0.1stake를 수수료의 최소 단위로 한다.
노드가 이 설정치보다 작은gas-price가 수수료를 설정한 거래를 받아들이지 않는다는 것을 기억하세요.
app.toml
halt-height = 0
halt-time = 0
index-events = []
inter-block-cache = true
min-retain-blocks = 0
minimum-gas-prices = "0.1stake" // <- 変更
pruning = "default"
pruning-interval = "0"
pruning-keep-every = "0"
pruning-keep-recent = "0"
[api]
토큰 발송
마지막으로 Alice 및 Bob의 토큰을 표시하는 기능이 설치되었습니다.
Alice와 Bob 사이에 각각의 토큰을 전송하는 기능이 이번에 설치되었습니다.
대략적인 절차는 다음과 같다.
graph LR A1(From Address) --> B(TxBody) A2(To Address) --> B A3(amount) --> B A4(Public Key) --> E(Auth Info) A6(Dummy Fee) --> E A7(Dummy Gas) --> E B --> F(TxBuilder) E --> F subgraph Add signature F --> G(TxBuilder with Signature) A5(Private Key) --> G end
- シミュレーション用トランザクションデータに変換し、API で必要な手数料の推定値を取得
- 取得した手数料推定値で再度トランザクションデータを作成し、チェーンにアナウンス
基本的なトランザクションの送信機能はcosmos-client-tsのExamplesを参照ください。これをベースに機能を追加していきます。
1-1. 발송자 주소에서 기본 계정 만들기
const account = await rest.cosmos.auth
.account(this.sdk, fromAddress)
.then(
(res) =>
res.data.account && cosmosclient.codec.unpackCosmosAny(res.data.account)
);
이것은 & Typescript의 단락 평가 (단락 회로) 이다.res.data.계정에 값을 올바르게 입력한 경우 unpack Cosmosany를 실행합니다.오류 처리.1-2. 메시지 만들기
메시지에는 보낸 사람 주소, 보낸 사람 주소 및 보낸 내용이 포함됩니다.
// build MsgSend
const msgSend = new proto.cosmos.bank.v1beta1.MsgSend({
from_address: fromAddress.toString(),
to_address: this.toAddress && this.toAddress.toString(),
amount: [sendTokens],
});
1-3. Transaction으로 변환
1-2에서 만든 메시지를 사무 본문으로 변환합니다.
// build TxBody
const txBody = new proto.cosmos.tx.v1beta1.TxBody({
messages: [cosmosclient.codec.packAny(msgSend)],
});
2-1. 서명자 정보 정의
서명자의 정보를 정의합니다.이 서명은 영패 지불을 받은 사람, 즉 발송자의 주소를 의미한다.
// build authInfo for simulation
const authInfoSim = new proto.cosmos.tx.v1beta1.AuthInfo({
signer_infos: [
{
public_key: cosmosclient.codec.packAny(publicKey),
mode_info: {
single: {
mode: proto.cosmos.tx.signing.v1beta1.SignMode.SIGN_MODE_DIRECT,
},
},
sequence: account.sequence,
},
],
fee: {
amount: [{ denom: this.gasDenom, amount: "1" }], // <-- dummy fee
gas_limit: cosmosclient.Long.fromString("200000"), // <-- dummy gas
},
});
서명자 정보를 정의할 때 수수료를 지정해야 하지만 이때 필요한 수수료를 모르기 때문에 임시치를 사용한 것에 주의하십시오.(위와 같은dummy fee,dummy gas의 의견)2-2. 사무 데이터에 서명 데이터 추가
아날로그 사무를 할 때 적당한 서명자가 서명해야 하기 때문에 서명 데이터를 사무 데이터에 추가합니다.
// sign
const txBuilderSim = new cosmosclient.TxBuilder(sdk, txBody, authInfoSim);
const signDocBytesSim = txBuilderSim.signDocBytes(account.account_number);
txBuilderSim.addSignature(privateKey.sign(signDocBytesSim)); // <- 署名
2-3. 필요한gas의 추정치 가져오기
우선, 사무 데이터를 아날로그 데이터 형식으로 변환합니다.
필요 없는 데이터가 생성되므로 필요 없는 데이터가 삭제되어 데이터가 조정됩니다.
시뮬레이션 데이터가 준비되면 트랜잭션 시뮬레이션 API를 실행하여 필요한 가스 추정치를 가져옵니다.
// restore json from txBuilder
const txForSimulation = JSON.parse(txBuilderSim.cosmosJSONStringify());
delete txForSimulation.auth_info.signer_infos[0].mode_info.multi;
// simulate
const simulatedResult = await rest.tx.simulate(sdk, {
tx: txForSimulation,
tx_bytes: txBuilderSim.txBytes(),
});
// estimate gas
const simulatedGasUsed = simulatedResult.data.gas_info?.gas_used; // <- gasの推定必要量
3-1. 트랜잭션 서명 재정의하기
여기까지의 과정에서 가스의 추정 필요량을 얻었다.
그러나 이 값을 직접 사용하면 오차가 예상되는gas가 부족하여 거래도 실패할 수 있다.
따라서 실제 거래를 보낼 때는 여유율이 있을 것으로 추정되는 수치를 설정하는 것이 좋다.
다음은 다음 설정치를 사용하여 거래소에 필요한 각종 데이터를 재생성하는 것을 설명한다.
simulatedGasUsedWithMargin = simulatedGasUsed * 1.1
... API에서 확인한 가스의 추정치에 잔량률 1.1의 수치를 더한다.소수점 이하 반올림.simulatedFeeWithMargin = simulatedGasUsedWithMargin * gasPrice
... 상기simulatedGasUsedWithMargin
에 1가스당 단가gasPrice
를 곱하다.소수점 이하 반올림.gasPrice
로컬 체인의 수수료 설정minimumGasPrices
을 초과하지 않을 경우 노드는 거래를 거절합니다.따라서 노드
minimumGasPrices
의 설정치를 알고 있는 경우gasPrice
에 이 값을 사용하는 것이 합리적이다.// build authInfo
const authInfo = new proto.cosmos.tx.v1beta1.AuthInfo({
signer_infos: [
{
public_key: cosmosclient.codec.packAny(publicKey),
mode_info: {
single: {
mode: proto.cosmos.tx.signing.v1beta1.SignMode.SIGN_MODE_DIRECT,
},
},
sequence: account.sequence,
},
],
fee: {
amount: [{ denom: this.gasDenom, amount: simulatedFeeWithMargin }], // <-- 推測値を反映
gas_limit: cosmosclient.Long.fromString(
simulatedGasUsedWithMargin ? simulatedGasUsedWithMargin : "200000" // <-- 推測値を反映
),
},
});
fee->수수료로 소모되는 영패의 종류와 수량gas_limit->수수료로 소모되는gas의 양
단위는 다르지만 둘 다 수수료 설정을 표시한 점에 유의하시기 바랍니다.
다음 조건이 충족되면 노드가 거래를 받아들인다.
gas_limit
에서 설정한 값은 거래의 사이즈에 의해 결정되는gas필요량fee.amount
에 설정된 값 초과gas_limitに設定した値 * ノードのminimumGasPricesの設定値
3-2. 사무를 통고하다.
2-2와 같은 서명을 한 후 서명한 편지를 노드에 보냅니다.이 거래가 노드에 들어갈 때 블록체인에 거래 정보를 새기고 거래(영패의 발송)가 성립된다.
// broadcast
const res = await rest.cosmos.tx.broadcastTx(this.sdk, {
tx_bytes: txBuilder.txBytes(),
mode: rest.cosmos.tx.BroadcastTxMode.Block,
});
이루어지다
환경 구축 중인 모듈 도입을 제외하고저번 제작된 앱입니다.component.ts、app.component.>을(를) 다음 코드로 변경하여 작업을 확인합니다.
샘플 코드이기 때문에 고조파를 엄격하게 인코딩하고 있습니다.
고조파가 유출되면 계좌 잔액을 포함한 모든 통제를 빼앗길 수 있으니 설치 시 주의하시기 바랍니다.
app.component.ts
app.component.ts
import { Component, OnInit } from '@angular/core';
import { combineLatest, of, Observable, timer } from 'rxjs';
import { mergeMap } from 'rxjs/operators';
import { cosmosclient, proto, rest } from '@cosmos-client/core';
import { InlineResponse20028Balances } from '@cosmos-client/core/cjs/openapi/api';
import { AccAddress } from '@cosmos-client/core/cjs/types/address/acc-address';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
nodeURL = 'http://localhost:1317';
chainID = 'mars';
denoms = ['stake', 'token'];
gasPrice = '0.1';
gasDenom = this.denoms[0];
mnemonicA =
'power cereal remind render enhance muffin kangaroo snow hill nature bleak defense summer crisp scare muscle tiger dress behave verb pond merry voyage already';
mnemonicB =
'funny jungle scout crisp tissue dish talk tattoo alone scheme clog kiwi delay property current argue conduct west bounce reason abandon coral lawsuit hunt';
//pubkey ***サンプルコードのため、ニーモニックをハードコーディングしています。***
// ***アカウントのすべてのコントロールを渡すことになるので、決してマネしないよう。***
balancesAlice$: Observable<InlineResponse20028Balances[] | undefined>;
accAddressAlice: cosmosclient.AccAddress;
balancesBob$: Observable<InlineResponse20028Balances[] | undefined>;
accAddressBob: cosmosclient.AccAddress;
sdk$: Observable<cosmosclient.CosmosSDK> = of(
new cosmosclient.CosmosSDK(this.nodeURL, this.chainID)
);
timer$: Observable<number> = timer(0, 3 * 1000);
constructor() {
//Aliceの所持tokenを取得
this.accAddressAlice = cosmosclient.AccAddress.fromString(
'cosmos1lhaml37gselnnthjh9q2av2pkyf9hh67zy9maz'
);
this.balancesAlice$ = combineLatest(this.timer$, this.sdk$).pipe(
mergeMap(([n, sdk]) => {
return rest.bank
.allBalances(sdk, this.accAddressAlice)
.then((res) => res.data.balances);
})
);
//Bobの所持tokenを取得
this.accAddressBob = cosmosclient.AccAddress.fromString(
'cosmos1jwk3yttut7645kxwnuehkkzey2ztph9zklsu7u'
);
this.balancesBob$ = combineLatest(this.timer$, this.sdk$).pipe(
mergeMap(([n, sdk]) => {
return rest.bank
.allBalances(sdk, this.accAddressBob)
.then((res) => res.data.balances);
})
);
}
ngOnInit(): void {}
//txを送信
async sendTx(
mnemonic: string,
sdk_in: Observable<cosmosclient.CosmosSDK>,
toAddress: AccAddress,
denom: string,
amount: string
): Promise<void> {
//sdk
const sdk = await sdk_in.toPromise();
const sendTokens: proto.cosmos.base.v1beta1.ICoin = {
denom: denom,
amount: amount,
};
//Address
const privateKey = new proto.cosmos.crypto.secp256k1.PrivKey({
key: await cosmosclient.generatePrivKeyFromMnemonic(mnemonic),
});
const publicKey = privateKey.pubKey();
const fromAddress: AccAddress =
cosmosclient.AccAddress.fromPublicKey(publicKey);
// get account info
const account = await rest.auth
.account(sdk, fromAddress)
.then(
(res) =>
res.data.account &&
cosmosclient.codec.unpackCosmosAny(res.data.account)
)
.catch((_) => undefined);
if (!(account instanceof proto.cosmos.auth.v1beta1.BaseAccount)) {
throw Error('Address not found');
}
// build MsgSend
const msgSend = new proto.cosmos.bank.v1beta1.MsgSend({
from_address: fromAddress.toString(),
to_address: toAddress.toString(),
amount: [sendTokens],
});
// build TxBody
const txBody = new proto.cosmos.tx.v1beta1.TxBody({
messages: [cosmosclient.codec.packAny(msgSend)],
});
//Check fee -> ////////////////////////////////////////
// build authInfo for simulation
const authInfoSim = new proto.cosmos.tx.v1beta1.AuthInfo({
signer_infos: [
{
public_key: cosmosclient.codec.packAny(publicKey),
mode_info: {
single: {
mode: proto.cosmos.tx.signing.v1beta1.SignMode.SIGN_MODE_DIRECT,
},
},
sequence: account.sequence,
},
],
fee: {
amount: [{ denom: this.gasDenom, amount: '1' }],
gas_limit: cosmosclient.Long.fromString('200000'),
},
});
// sign for simulation
const txBuilderSim = new cosmosclient.TxBuilder(sdk, txBody, authInfoSim);
const signDocBytesSim = txBuilderSim.signDocBytes(account.account_number);
txBuilderSim.addSignature(privateKey.sign(signDocBytesSim));
// restore json from txBuilder
const txForSimulation = JSON.parse(txBuilderSim.cosmosJSONStringify());
// fix JSONstringify issue
delete txForSimulation.auth_info.signer_infos[0].mode_info.multi;
// simulate
const simulatedResult = await rest.tx.simulate(sdk, {
tx: txForSimulation,
tx_bytes: txBuilderSim.txBytes(),
});
console.log('simulatedResult', simulatedResult);
// estimate fee
const simulatedGasUsed = simulatedResult.data.gas_info?.gas_used;
// This margin prevents insufficient fee due to data size difference between simulated tx and actual tx.
const simulatedGasUsedWithMarginNumber = simulatedGasUsed
? parseInt(simulatedGasUsed) * 1.1
: 200000;
const simulatedGasUsedWithMargin =
simulatedGasUsedWithMarginNumber.toFixed(0);
// minimumGasPrice depends on Node's config(`~/.mars/config/app.toml` minimum-gas-prices).
const simulatedFeeWithMarginNumber =
parseInt(simulatedGasUsedWithMargin) * parseFloat(this.gasPrice);
const simulatedFeeWithMargin = Math.ceil(
simulatedFeeWithMarginNumber
).toFixed(0);
console.log({
simulatedGasUsed,
simulatedGasUsedWithMargin,
simulatedFeeWithMarginNumber,
simulatedFeeWithMargin,
});
//Check fee <- ////////////////////////////////////////
// build authInfo
const authInfo = new proto.cosmos.tx.v1beta1.AuthInfo({
signer_infos: [
{
public_key: cosmosclient.codec.packAny(publicKey),
mode_info: {
single: {
mode: proto.cosmos.tx.signing.v1beta1.SignMode.SIGN_MODE_DIRECT,
},
},
sequence: account.sequence,
},
],
fee: {
amount: [{ denom: this.gasDenom, amount: simulatedFeeWithMargin }],
gas_limit: cosmosclient.Long.fromString(
simulatedGasUsedWithMargin ? simulatedGasUsedWithMargin : '200000'
),
},
});
// sign for transaction
const txBuilder = new cosmosclient.TxBuilder(sdk, txBody, authInfo);
const signDocBytes = txBuilder.signDocBytes(account.account_number);
txBuilder.addSignature(privateKey.sign(signDocBytes));
// broadcast
const res = await rest.tx.broadcastTx(sdk, {
tx_bytes: txBuilder.txBytes(),
mode: rest.tx.BroadcastTxMode.Block,
});
console.log('tx_res', res);
}
}
app.component.htmlapp.component.html
<div class="grid-container">
<h1 class="mat-h1">My-app</h1>
<mat-grid-list cols="2" rowHeight="450px">
<!--Bob-->
<mat-grid-tile [colspan]="1" [rowspan]="1">
<mat-card class="dashboard-card">
<mat-card-header>
<mat-card-title> Bob </mat-card-title>
</mat-card-header>
<mat-card-content class="dashboard-card-content">
<h3>
<div>address : {{ this.accAddressBob }}</div>
</h3>
<!--所持tokenを表示-->
<ng-container *ngIf="balancesBob$ | async as balancesBob">
<h3>balances</h3>
<ng-container *ngFor="let balanceBob of balancesBob">
<div>
token: {{ balanceBob.denom }} balance: {{ balanceBob.amount }}
</div>
</ng-container>
</ng-container>
<!--送信フォーム-->
<form
#formB="ngForm"
class="mt-4"
(submit)="
sendTx(
mnemonicB,
sdk$,
accAddressAlice,
denomBRef.value,
amountBRef.value
)
"
>
<mat-form-field appearance="fill">
<mat-label>amount</mat-label>
<input
#amountBRef
matInput
required
id="amountValue"
name="amountValue"
/>
</mat-form-field>
<mat-select
#denomBRef
placeholder="Select"
name="denomOption"
required
>
<mat-option *ngFor="let denom of denoms" [value]="denom">
{{ denom }}
</mat-option>
</mat-select>
<button mat-flat-button color="primary">Send</button>
</form>
</mat-card-content>
</mat-card>
</mat-grid-tile>
<!--Alice-->
<mat-grid-tile [colspan]="1" [rowspan]="1">
<mat-card class="dashboard-card">
<mat-card-header>
<mat-card-title>Alice</mat-card-title>
</mat-card-header>
<mat-card-content class="dashboard-card-content">
<h3>
<div>address : {{ this.accAddressAlice }}</div>
</h3>
<!--所持tokenを表示-->
<ng-container *ngIf="balancesAlice$ | async as balancesAlice">
<h3>balances</h3>
<ng-container *ngFor="let balanceAlice of balancesAlice">
<div>
token: {{ balanceAlice.denom }} balance:
{{ balanceAlice.amount }}
</div>
</ng-container>
</ng-container>
<!--送信フォーム-->
<form
#formA="ngForm"
class="mt-4"
(submit)="
sendTx(
mnemonicA,
sdk$,
accAddressBob,
denomARef.value,
amountARef.value
)
"
>
<mat-form-field appearance="fill">
<mat-label>amount</mat-label>
<input
#amountARef
matInput
required
id="amountValue"
name="amountValue"
/>
</mat-form-field>
<mat-select
#denomARef
placeholder="Select"
name="denomOption"
required
>
<mat-option *ngFor="let denom of denoms" [value]="denom">
{{ denom }}
</mat-option>
</mat-select>
<button mat-flat-button color="accent">Send</button>
</form>
</mat-card-content>
</mat-card>
</mat-grid-tile>
</mat-grid-list>
</div>
동작 확인
balancess 표시줄에 아무것도 표시되지 않으면 체인이 세워져 있는지 확인하십시오.
센드 버튼을 누르면 서로 토큰을 보낼 수 있다.
7token을 Alice에서 Bob으로 보내보세요.환경 구축에서 미니멀-가스-price에 스테이크가 설정되어 있기 때문에 스테이크는 수수료로 사용해야 한다.
<보내기 전>
<보낸 후>
앨리스에서 밥에게 세븐토큰을 보낸 것을 보면 스테이크가 소모됐음을 알 수 있다.
~/.mars/config/app.toml의 minim-gas-price, app.component.ts의gasProice를 변경하고 수수료의 변화를 확인하며 미니멀-gas-Pice>gasProice는 거래를 발송할 수 없습니다.
총결산
로컬 체인의 수수료 설정과 API를 사용하는 적정 수수료 설정에 대해 샘플 코드의 실상과 동작 확인을 진행했다.
무심코 기호화폐를 보내도 의외로 하고 있는 일들이 다양하게 전달되면 좋겠다.
CauchyE 같이 일하는 사람 기다리고 있어!
블록체인과 데이터 과학에 관심 있는 엔지니어를 적극 채용하고 있습니다!
다음 페이지에서 응모를 기다리십시오.
Reference
이 문제에 관하여(Anglar,cosmos-client의 블록체인 웹 응용 프로그램 프런트엔드 개발2(추가 전송 기능)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/cauchye/articles/20211206_tsunoda텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)