Skip to content

Latest commit

 

History

History
532 lines (459 loc) · 30.9 KB

example.md

File metadata and controls

532 lines (459 loc) · 30.9 KB

Example

Cosmjs 주요 모듈 별 제공 함수 & 인터페이스

아래 링크에서 쉽게 찾으실 수 있습니다.

cosmjs/stargate

Data Type

export interface BlockHeader {
  readonly version: {
    readonly block: string;
    readonly app: string;
  };
  readonly height: number;
  readonly chainId: string;
  /** An RFC 3339 time string like e.g. '2020-02-15T10:39:10.4696305Z' */
  readonly time: string;
}

export interface Block {
  /** The ID is a hash of the block header (uppercase hex) */
  readonly id: string;
  readonly header: BlockHeader;
  /** Array of raw transactions */
  readonly txs: readonly Uint8Array[];
}

/** A transaction that is indexed as part of the transaction history */
export interface IndexedTx {
  readonly height: number;
  /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
  readonly hash: string;
  /** Transaction execution error code. 0 on success. */
  readonly code: number;
  readonly rawLog: string;
  /**
   * Raw transaction bytes stored in Tendermint.
   *
   * If you hash this, you get the transaction hash (= transaction ID):
   *
   * ```js
   * import { sha256 } from "@cosmjs/crypto";
   * import { toHex } from "@cosmjs/encoding";
   *
   * const transactionId = toHex(sha256(indexTx.tx)).toUpperCase();
   * ```
   *
   * Use `decodeTxRaw` from @cosmjs/proto-signing to decode this.
   */
  readonly tx: Uint8Array;
  readonly gasUsed: number;
  readonly gasWanted: number;
}

export interface SequenceResponse {
  readonly accountNumber: number;
  readonly sequence: number;
}

/**
 * The response after successfully broadcasting a transaction.
 * Success or failure refer to the execution result.
 */
export interface DeliverTxResponse {
  readonly height: number;
  /** Error code. The transaction suceeded iff code is 0. */
  readonly code: number;
  readonly transactionHash: string;
  readonly rawLog?: string;
  readonly data?: readonly MsgData[];
  readonly gasUsed: number;
  readonly gasWanted: number;
}

export function isDeliverTxFailure(result: DeliverTxResponse): boolean {
  return !!result.code;
}

Function

function/method parameter return description
isDeliverTxFailure result(DeliverTxResponse) boolean
isDeliverTxSuccess result(DeliverTxResponse) boolean
assertIsDeliverTxSuccess result(DeliverTxResponse) None Throw Error. Ensures the given result is a success. Throws a detailed error message otherwise.
assertIsDeliverTxFailure result(DeliverTxResponse) None Throw Error. Ensures the given result is a success. Throws a detailed error message otherwise.
connect

endpoint(string

HttpEndpoint)
options(StargateClientOptions)

Promise<StargateClient>
getTmClient None

Tendermint34Client
undefined

forceGetTmClient None Tendermint34Client
getQueryClient None

QueryClient & AuthExtension & BankExtension & StakingExtension & TxExtension
undefined

forceGetQueryClient None QueryClient & AuthExtension & BankExtension & StakingExtension & TxExtension
getChainId None Promise<string> return chain ID
getHeight None Promise<number>
getAccount searchAddress(string) Promise<Account | null>
getSequence address(string) Promise<SequenceResponse>
getBlock height(number) Promise<Block>
getBalance

address(string)
searchDenom(string)

Promise<Coin>
getAllBalances address(string) Promise<Coin[]>
getBalanceStaked address(string) Promise<Coin | null>
getDelegation

delegatorAddress(string)
validatorAddress(string)

Promise<Coin | null>
getTx id(string) Promise<IndexedTx | null>
searchTx

query(SearchTxQuery)
filter(searchTxFilter)

Promise<IndexedTx[]>
disconnect None None
broadcastTx

tx(Uint8Array)
timeoutMs(number)
pollIntervalMs(number)

Promise<DeliverTxResponse> timeoutMs’s default value = 60_000 pollIntervalMs = 3_000
txsQuery query(string) Promise<IndexedTx[]>
export function isDeliverTxFailure(result: DeliverTxResponse): boolean {
  return !!result.code;
}

export function isDeliverTxSuccess(result: DeliverTxResponse): boolean {
  return !isDeliverTxFailure(result);
}

/**
 * Ensures the given result is a success. Throws a detailed error message otherwise.
 */
export function assertIsDeliverTxSuccess(result: DeliverTxResponse): void {
  if (isDeliverTxFailure(result)) {
    throw new Error(
      `Error when broadcasting tx ${result.transactionHash} at height ${result.height}. Code: ${result.code}; Raw log: ${result.rawLog}`,
    );
  }
}

/**
 * Ensures the given result is a failure. Throws a detailed error message otherwise.
 */
export function assertIsDeliverTxFailure(result: DeliverTxResponse): void {
  if (isDeliverTxSuccess(result)) {
    throw new Error(
      `Transaction ${result.transactionHash} did not fail at height ${result.height}. Code: ${result.code}; Raw log: ${result.rawLog}`,
    );
  }
}

/** Use for testing only */
export interface PrivateStargateClient {
  readonly tmClient: Tendermint34Client | undefined;
}

export interface StargateClientOptions {
  readonly accountParser?: AccountParser;
}

export class StargateClient {
  private readonly tmClient: Tendermint34Client | undefined;
  private readonly queryClient:
    | (QueryClient & AuthExtension & BankExtension & StakingExtension & TxExtension)
    | undefined;
  private chainId: string | undefined;
  private readonly accountParser: AccountParser;

  public static async connect(
    endpoint: string | HttpEndpoint,
    options: StargateClientOptions = {},
  ): Promise<StargateClient> {
    const tmClient = await Tendermint34Client.connect(endpoint);
    return new StargateClient(tmClient, options);
  }

  protected constructor(tmClient: Tendermint34Client | undefined, options: StargateClientOptions) {
    if (tmClient) {
      this.tmClient = tmClient;
      this.queryClient = QueryClient.withExtensions(
        tmClient,
        setupAuthExtension,
        setupBankExtension,
        setupStakingExtension,
        setupTxExtension,
      );
    }
    const { accountParser = accountFromAny } = options;
    this.accountParser = accountParser;
  }

  protected getTmClient(): Tendermint34Client | undefined {
    return this.tmClient;
  }

  protected forceGetTmClient(): Tendermint34Client {
    if (!this.tmClient) {
      throw new Error(
        "Tendermint client not available. You cannot use online functionality in offline mode.",
      );
    }
    return this.tmClient;
  }

  protected getQueryClient():
    | (QueryClient & AuthExtension & BankExtension & StakingExtension & TxExtension)
    | undefined {
    return this.queryClient;
  }

  protected forceGetQueryClient(): QueryClient &
    AuthExtension &
    BankExtension &
    StakingExtension &
    TxExtension {
    if (!this.queryClient) {
      throw new Error("Query client not available. You cannot use online functionality in offline mode.");
    }
    return this.queryClient;
  }

  public async getChainId(): Promise<string> {
    if (!this.chainId) {
      const response = await this.forceGetTmClient().status();
      const chainId = response.nodeInfo.network;
      if (!chainId) throw new Error("Chain ID must not be empty");
      this.chainId = chainId;
    }

    return this.chainId;
  }

  public async getHeight(): Promise<number> {
    const status = await this.forceGetTmClient().status();
    return status.syncInfo.latestBlockHeight;
  }

  public async getAccount(searchAddress: string): Promise<Account | null> {
    try {
      const account = await this.forceGetQueryClient().auth.account(searchAddress);
      return account ? this.accountParser(account) : null;
    } catch (error: any) {
      if (/rpc error: code = NotFound/i.test(error.toString())) {
        return null;
      }
      throw error;
    }
  }

  public async getSequence(address: string): Promise<SequenceResponse> {
    const account = await this.getAccount(address);
    if (!account) {
      throw new Error(
        "Account does not exist on chain. Send some tokens there before trying to query sequence.",
      );
    }
    return {
      accountNumber: account.accountNumber,
      sequence: account.sequence,
    };
  }

  public async getBlock(height?: number): Promise<Block> {
    const response = await this.forceGetTmClient().block(height);
    return {
      id: toHex(response.blockId.hash).toUpperCase(),
      header: {
        version: {
          block: new Uint53(response.block.header.version.block).toString(),
          app: new Uint53(response.block.header.version.app).toString(),
        },
        height: response.block.header.height,
        chainId: response.block.header.chainId,
        time: toRfc3339WithNanoseconds(response.block.header.time),
      },
      txs: response.block.txs,
    };
  }

  public async getBalance(address: string, searchDenom: string): Promise<Coin> {
    return this.forceGetQueryClient().bank.balance(address, searchDenom);
  }

  /**
   * Queries all balances for all denoms that belong to this address.
   *
   * Uses the grpc queries (which iterates over the store internally), and we cannot get
   * proofs from such a method.
   */
  public async getAllBalances(address: string): Promise<readonly Coin[]> {
    return this.forceGetQueryClient().bank.allBalances(address);
  }

  public async getBalanceStaked(address: string): Promise<Coin | null> {
    const allDelegations = [];
    let startAtKey: Uint8Array | undefined = undefined;
    do {
      const { delegationResponses, pagination }: QueryDelegatorDelegationsResponse =
        await this.forceGetQueryClient().staking.delegatorDelegations(address, startAtKey);

      const loadedDelegations = delegationResponses || [];
      allDelegations.push(...loadedDelegations);
      startAtKey = pagination?.nextKey;
    } while (startAtKey !== undefined && startAtKey.length !== 0);

    const sumValues = allDelegations.reduce(
      (previousValue: Coin | null, currentValue: DelegationResponse): Coin => {
        // Safe because field is set to non-nullable (https://github.com/cosmos/cosmos-sdk/blob/v0.45.3/proto/cosmos/staking/v1beta1/staking.proto#L295)
        assert(currentValue.balance);
        return previousValue !== null ? addCoins(previousValue, currentValue.balance) : currentValue.balance;
      },
      null,
    );

    return sumValues;
  }

  public async getDelegation(delegatorAddress: string, validatorAddress: string): Promise<Coin | null> {
    let delegatedAmount: Coin | undefined;
    try {
      delegatedAmount = (
        await this.forceGetQueryClient().staking.delegation(delegatorAddress, validatorAddress)
      ).delegationResponse?.balance;
    } catch (e: any) {
      if (e.toString().includes("key not found")) {
        // ignore, `delegatedAmount` remains undefined
      } else {
        throw e;
      }
    }
    return delegatedAmount || null;
  }

  public async getTx(id: string): Promise<IndexedTx | null> {
    const results = await this.txsQuery(`tx.hash='${id}'`);
    return results[0] ?? null;
  }

  public async searchTx(query: SearchTxQuery, filter: SearchTxFilter = {}): Promise<readonly IndexedTx[]> {
    const minHeight = filter.minHeight || 0;
    const maxHeight = filter.maxHeight || Number.MAX_SAFE_INTEGER;

    if (maxHeight < minHeight) return []; // optional optimization

    function withFilters(originalQuery: string): string {
      return `${originalQuery} AND tx.height>=${minHeight} AND tx.height<=${maxHeight}`;
    }

    let txs: readonly IndexedTx[];

    if (isSearchByHeightQuery(query)) {
      txs =
        query.height >= minHeight && query.height <= maxHeight
          ? await this.txsQuery(`tx.height=${query.height}`)
          : [];
    } else if (isSearchBySentFromOrToQuery(query)) {
      const sentQuery = withFilters(`message.module='bank' AND transfer.sender='${query.sentFromOrTo}'`);
      const receivedQuery = withFilters(
        `message.module='bank' AND transfer.recipient='${query.sentFromOrTo}'`,
      );
      const [sent, received] = await Promise.all(
        [sentQuery, receivedQuery].map((rawQuery) => this.txsQuery(rawQuery)),
      );
      const sentHashes = sent.map((t) => t.hash);
      txs = [...sent, ...received.filter((t) => !sentHashes.includes(t.hash))];
    } else if (isSearchByTagsQuery(query)) {
      const rawQuery = withFilters(query.tags.map((t) => `${t.key}='${t.value}'`).join(" AND "));
      txs = await this.txsQuery(rawQuery);
    } else {
      throw new Error("Unknown query type");
    }

    const filtered = txs.filter((tx) => tx.height >= minHeight && tx.height <= maxHeight);
    return filtered;
  }

  public disconnect(): void {
    if (this.tmClient) this.tmClient.disconnect();
  }

  /**
   * Broadcasts a signed transaction to the network and monitors its inclusion in a block.
   *
   * If broadcasting is rejected by the node for some reason (e.g. because of a CheckTx failure),
   * an error is thrown.
   *
   * If the transaction is not included in a block before the provided timeout, this errors with a `TimeoutError`.
   *
   * If the transaction is included in a block, a `DeliverTxResponse` is returned. The caller then
   * usually needs to check for execution success or failure.
   */
  public async broadcastTx(
    tx: Uint8Array,
    timeoutMs = 60_000,
    pollIntervalMs = 3_000,
  ): Promise<DeliverTxResponse> {
    let timedOut = false;
    const txPollTimeout = setTimeout(() => {
      timedOut = true;
    }, timeoutMs);

    const pollForTx = async (txId: string): Promise<DeliverTxResponse> => {
      if (timedOut) {
        throw new TimeoutError(
          `Transaction with ID ${txId} was submitted but was not yet found on the chain. You might want to check later. There was a wait of ${
            timeoutMs / 1000
          } seconds.`,
          txId,
        );
      }
      await sleep(pollIntervalMs);
      const result = await this.getTx(txId);
      return result
        ? {
            code: result.code,
            height: result.height,
            rawLog: result.rawLog,
            transactionHash: txId,
            gasUsed: result.gasUsed,
            gasWanted: result.gasWanted,
          }
        : pollForTx(txId);
    };

    const broadcasted = await this.forceGetTmClient().broadcastTxSync({ tx });
    if (broadcasted.code) {
      return Promise.reject(
        new Error(
          `Broadcasting transaction failed with code ${broadcasted.code} (codespace: ${broadcasted.codeSpace}). Log: ${broadcasted.log}`,
        ),
      );
    }
    const transactionId = toHex(broadcasted.hash).toUpperCase();
    return new Promise((resolve, reject) =>
      pollForTx(transactionId).then(
        (value) => {
          clearTimeout(txPollTimeout);
          resolve(value);
        },
        (error) => {
          clearTimeout(txPollTimeout);
          reject(error);
        },
      ),
    );
  }

  private async txsQuery(query: string): Promise<readonly IndexedTx[]> {
    const results = await this.forceGetTmClient().txSearchAll({ query: query });
    return results.txs.map((tx) => {
      return {
        height: tx.height,
        hash: toHex(tx.hash).toUpperCase(),
        code: tx.result.code,
        rawLog: tx.result.log || "",
        tx: tx.tx,
        gasUsed: tx.result.gasUsed,
        gasWanted: tx.result.gasWanted,
      };
    });
  }
}

cosmjs/cosmwasm-stargate

function/method parameter return
connectWithSigner

endPoint(string

HttpEndpoint)
signer(OfflineSigner)
options(SigningCosmWasmClientOptions)

offline

signer(OfflineSigner)
options(SigningCosmWasmClientOptions)

Promise<SigningCosmWasmClient>
simulate

signerAddress(string)
messages(readonly EncodeObject[])
memo(string

undefined)

upload

senderAddress(string)
wasmCode(Uint8Array)
fee(StdFee

"auto"
instantiate

senderAddress(string)
codeId(number)
msg(Record<string, unknown>)
label(string)
fee(StdFee

"auto"
updateAdmin

senderAddress(string)
contractAddress(string)
newAdmin(string)
fee(StdFee

"auto"
clearAdmin

senderAddress(string)
contractAddress(string)
fee(StdFee

"auto"
migrate

senderAddress(string)
contractAddress(string)
codeId(number)
migrateMsg(Record<string, unknown>)
fee(StdFee

"auto"
execute

senderAddress(string)
contractAddress(string)
msg(Record<string, unknown>)
fee(StdFee

"auto"
executeMultiple

senderAddress(string)
instructions(readonly ExecuteInstruction[])
fee(StdFee

"auto"
sendTokens

senderAddress(string)
recipientAddress(string)
amount(readonly Coin[])
fee(StdFee

"auto"
delegateTokens

delegatorAddress(string)
validatorAddress(string)
amount(Coin)
fee(StdFee

"auto"
undelegateTokens

delegatorAddress(string)
validatorAddress(string)
amount(Coin)
fee(StdFee

"auto"
withdrawRewards

delegatorAddress(string)
validatorAddress(string)
fee(StdFee

"auto"
signAndBroadcast

signerAddress(string)
messages(readonly EncodeObject[])
fee(StdFee

"auto"
sign

signerAddress(string)
messages(readonly EncodeObject[])
fee(StdFee)
memo(string)
explicitSignerData?(SignerData)

Promise<TxRaw>

cosmjs/encoding

이 패키지는 블록체인 제품에 얽매이지 않는 자바스크립트 표준 라이브러리의 확장입니. 버퍼에 의존하지 않는 Uint8Array에 기본 16진수/베이스64/ascii 인코딩을 제공하고 잘못된 입력 시 더 나은 오류 메시지를 제공합니다.

Code Example

import { toBech32, toHex, fromHex, fromBech32 } from "@cosmos/encoding";

const example1 =  toBech32("tiov", fromHex("1234ABCD0000AA0000FFFF0000AA00001234ABCD"))
'tiov1zg62hngqqz4qqq8lluqqp2sqqqfrf27dzrrmea';
const example2 = toHex(fromBech32("tiov1zg62hngqqz4qqq8lluqqp2sqqqfrf27dzrrmea").data)
'1234abcd0000aa0000ffff0000aa00001234abcd';

Why encoding is needed?

여러 주소 표시 체계가 있는데 해당 패키지에서는 Bech32, Hex, Ascii, Base64, RFC3339, UTF8 간의 encoding을 지원합니다.

Bech32란?

Bech32는 BIP 0173에서 지정한 세그먼트 주소 형식입니다. 이 주소 형식은 "bc1 주소"라고도 합니다. Bech32는 블록 공간이 더 효율적입니다. 2020년 10월 기준으로 Bech32 주소 형식은 많은 대중적인 지갑에서 지원되며 선호되는 주소 체계입니다.

이렇게 블록체제에서 Bech32가 유리하지만 TxHash 등은 Hex이고 상황에 따라 사용해야하는 주소 체계가 있기 때문에 해당 모듈을 사용하면 이를 해결할 수 있습니다.

<제공하는 method 목록>

export { fromAscii, toAscii } from "./ascii";
export { fromBase64, toBase64 } from "./base64";
export { Bech32, fromBech32, normalizeBech32, toBech32 } from "./bech32";
export { fromHex, toHex } from "./hex";
export { fromRfc3339, toRfc3339 } from "./rfc3339";
export { fromUtf8, toUtf8 } from "./utf8";

모듈의 디테일한 코드, 기능에 대해 알고 싶다면 이곳에서 오픈소스를 참고하길 바랍니다.