cosmosjsでNameserviceに接続する
前回は Cosmos SDK チュートリアルの Namesevice チェーンをローカルにインストール,起動してみました.
今回は chainapsis が開発している cosmosjs というパッケージを使って,この Nameservice Chain に接続してみます.
前回の内容はこちら.
インストール
terminal
$ npm i @chainapsis/cosmosjs
コーデックの登録
Cosmos SDK でビルドされている Nameservice チェーンと相互にデータを読み書きするためには,エンコード / デコードが必要で,そのためにコーデックを登録します.
msgs.ts
import { Amino, Type } from '@node-a-team/ts-amino';
const { Field, DefineStruct } = Amino;
import { Msg } from '@chainapsis/cosmosjs/core/tx';
import { AccAddress } from '@chainapsis/cosmosjs/common/address';
import { Coin } from '@chainapsis/cosmosjs/common/coin';
@DefineStruct()
export class MsgSetName extends Msg {
@Field.String(0, {
jsonName: 'name'
})
public name: string;
@Field.String(1, {
jsonName: 'value'
})
public value: string;
@Field.Defined(2, {
jsonName: 'owner'
})
public owner: AccAddress;
constructor(name: string, value: string, owner: AccAddress) {
super();
this.name = name;
this.value = value;
this.owner = owner;
}
public getSigners(): AccAddress[] {
return [this.owner]
}
public validateBasic(): void {}
}
@DefineStruct()
export class MsgBuyName extends Msg {
@Field.String(0, {
jsonName: 'name'
})
public name: string
@Field.Slice(
1,
{ type: Type.Defined },
{
jsonName: 'bid'
}
)
public bid: Coin[]
;
@Field.Defined(2, {
jsonName: 'buyer'
})
public buyer: AccAddress;
constructor(name: string, bid: Coin[], buyer: AccAddress) {
super();
this.name = name;
this.bid = bid;
this.buyer = buyer;
}
public getSigners(): AccAddress[] {
return [this.buyer];
}
public validateBasic(): void {}
}
@DefineStruct()
export class MsgDeleteName extends Msg {
@Field.String(0, {
jsonName: 'name'
})
public name: string;
@Field.Defined(1, {
jsonName: 'owner'
})
public owner: AccAddress;
constructor(name: string, owner: AccAddress) {
super();
this.name = name;
this.owner = owner;
}
public getSigners(): AccAddress[] {
return [this.owner];
}
public validateBasic(): void {}
}
Nameservice チェーンのドキュメントと比較しながら適切に引数,コントラクトを書くとこのような感じになります.
引数を定義している @Field
の型は @node-a-team/ts-amino で確認できます.AccAddress
などの特殊な型は Defined
と置くことで実装できるようです.String
なのに Defined
とかで実装すると,エラーが出ます.
ここで定義したクラスをコーデックに登録するようにします.
codec.ts
import { Codec } from '@node-a-team/ts-amino';
import { MsgSetName, MsgBuyName, MsgDeleteName } from './msgs';
export function registerCodec(codec: Codec) {
codec.registerConcrete('nameservice/SetName', MsgSetName.prototype);
codec.registerConcrete('nameservice/BuyName', MsgBuyName.prototype);
codec.registerConcrete('nameservice/DeleteName', MsgDeleteName.prototype);
}
そして,これらのファイルを一括でインポートするためのファイルを用意します.
index.ts
export * from './msgs';
export * from './codec';
用意した3ファイルを nameservice
ディレクトリに入れることで,クラスを呼び出す際に以下のように書くことができます.
import { MsgSetName, MsgBuyName, MsgDeleteName } from "/path/to/nameservice";
API の作成
cosmosjs では Gaia API がデフォルトになっていますが,新機能をコーデックに登録したときなどは API も新たに作らなければなりません.コーデックが登録されていないからです.
customApi.ts
import { Api, ApiConfig, CoreConfig } from '@chainapsis/cosmosjs/core/api';
import * as CmnCdc from '@chainapsis/cosmosjs/common/codec';
import * as Bank from '@chainapsis/cosmosjs/x/bank';
import * as Distribution from '@chainapsis/cosmosjs/x/distribution';
import * as Gov from '@chainapsis/cosmosjs/x/gov';
import * as Slashing from '@chainapsis/cosmosjs/x/slashing';
import * as Staking from '@chainapsis/cosmosjs/x/staking';
import * as Nameservice from '/path/to/nameservice';
import { defaultTxEncoder } from '@chainapsis/cosmosjs/common/stdTx';
import { stdTxBuilder } from '@chainapsis/cosmosjs/common/stdTxBuilder';
import { Context } from '@chainapsis/cosmosjs/core/context';
import { Account } from '@chainapsis/cosmosjs/core/account';
import { BIP44 } from '@chainapsis/cosmosjs/core/bip44';
import { defaultBech32Config } from '@chainapsis/cosmosjs/core/bech32Config';
import { Codec } from '@chainapsis/ts-amino';
import { queryAccount } from '@chainapsis/cosmosjs/core/query';
import * as Crypto from '@chainapsis/cosmosjs/crypto';
import { CustomRest } from './customRest';
import * as Nameservice from '/path/to/nameservice';
export class CustomApi extends Api<CustomRest> {
constructor(
config: ApiConfig,
coreConfig: Partial<CoreConfig<CustomRest>> = {}
) {
super(config, {
...{
txEncoder: defaultTxEncoder,
txBuilder: stdTxBuilder,
restFactory: (context: Context) => {
return new CustomRest(context);
},
queryAccount: (
context: Context,
address: string | Uint8Array
): Promise<Account> => {
return queryAccount(
context.get('rpcInstance'),
address,
coreConfig.bech32Config
? coreConfig.bech32Config.bech32PrefixAccAddr
: 'cosmos'
);
},
bech32Config: defaultBech32Config('cosmos'),
bip44: new BIP44(44, 118, 0),
registerCodec: (codec: Codec) => {
CmnCdc.registerCodec(codec)
Crypto.registerCodec(codec)
Bank.registerCodec(codec)
Distribution.registerCodec(codec)
Gov.registerCodec(codec)
Slashing.registerCodec(codec)
Staking.registerCodec(codec)
Nameservice.registerCodec(codec)
}
},
...coreConfig
});
}
}
先ほど作成した nameservice
をインポートしてコーデックに登録します.
これで SetName, BuyName, DeleteName の機能を利用することができるようになりました.
Rest も Gaia REST を使わずにカスタマイズするので,この後に用意しますが先にインポートしておきます.
REST の作成
コーデックを登録することで Nameservice チェーンにトランザクションの内容を反映させることはできるようになりました.
後は,GET で Nameservice チェーンからデータを取得できれば OK です.
customRest.ts
import { Rest } from '@chainapsis/cosmosjs/core/rest';
import { Account } from '@chainapsis/cosmosjs/core/account';
import { AccAddress } from '@chainapsis/cosmosjs/common/address';
import { CustomBaseAccount } from '../customBaseAccount';
export class CustomRest extends Rest {
public async getAccount(
account: string | Uint8Array,
bech32PrefixAccAddr?: string
): Promise<Account> {
if (typeof account === 'string' && !bech32PrefixAccAddr) {
throw new Error('Empty bech32 prefix');
}
const accAddress: AccAddress =
typeof account === 'string'
? AccAddress.fromBech32(account, bech32PrefixAccAddr)
: new AccAddress(account, bech32PrefixAccAddr!);
const result = await this.instance.get(
`auth/accounts/${accAddress.toBech32()}`
);
return CustomBaseAccount.fromJSON(result.data);
}
public async resolveName(name: string): Promise<string> {
const result = await this.instance.get(`nameservice/names/${name}`);
return result.data;
}
public async whois(name: string): Promise<string> {
const result = await this.instance.get(`nameservice/names/${name}/whois`);
return result.data;
}
public async getNames(): Promise<string> {
const result = await this.instance.get('nameservice/names');
return result.data;
}
}
データは JSON で返ってきますが,とりあえず内容を確認するだけということでそのまま返しています.this.instance
は axios を呼んでいるので,簡単に実装できます.
customBaseAccount
を呼んでいますが,これはアカウントタイプがなぜか undefined
になってしまう問題を回避するために,全く同じ内容でローカルに用意しているだけなので割愛します.
ここまでくれば準備完了です.
TX の作成と取引実行
import { defaultBech32Config } from "@chainapsis/cosmosjs/core/bech32Config";
import { LocalWalletProvider } from "@chainapsis/cosmosjs/core/walletProvider";
import { AccAddress } from "@chainapsis/cosmosjs/common/address";
import { Coin } from "@chainapsis/cosmosjs/common/coin";
import { Int } from "@chainapsis/cosmosjs/common/int";
import bigInteger from "big-integer";
import { BIP44 } from "@chainapsis/cosmosjs/core/bip44";
import { CustomApi } from "/path/to/customApi";
import { CustomRest } from "/path/to/customRest";
import { MsgSetName, MsgBuyName, MsgDeleteName } from "/path/to/nameservice";
(async () => {
const wallet = new LocalWalletProvider("cosmos", this.jackMnemonic);
const api = new CustomApi({
chainId: "namechain",
walletProvider: wallet,
rpc: "http://localhost:26657",
rest: "http://localhost:1317",
});
// You should sign in before using your wallet
await api.enable();
const key = (await api.getKeys())[0];
const accAddress = new AccAddress(key.address, "cosmos");
const rest = new CustomRest(api.context);
const account = await rest.getAccount(key.address, "cosmos");
const resultBroadcastTxCommit = await api.sendMsgs(
[
new MsgBuyName(
"jack1.id",
[new Coin("nametoken", new Int("5"))],
AccAddress.fromBech32(this.jackWallet.address, "cosmos")
),
new MsgSetName(
"jack1.id",
"8.8.4.4",
AccAddress.fromBech32(this.jackWallet.address, "cosmos")
),
],
{
gas: bigInteger(80000),
memo: "test",
fee: new Coin("nametoken", new Int("11")),
},
"commit"
);
console.log(resultBroadcastTxCommit);
const result = await rest.resolveName("jack1.id");
console.log(result);
// output: 8.8.4.4
})();
今回は LedgerWalletProvider
ではなく LocalWalletProvider
を利用します.
チュートリアルにあった処理を CLI ではなく JS 上で実装すると,このようになります.
最後に
Cosmos SDK と合わせて使うことでブロックチェーンを利用したサービスを比較的簡単に作れそうです.