Merge pull request #1 from confio/support-multiple-tokens
Support multiple tokens
This commit is contained in:
commit
de52df68d4
12
.editorconfig
Normal file
12
.editorconfig
Normal file
@ -0,0 +1,12 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.py]
|
||||
indent_size = 4
|
||||
61
.eslintrc.json
Normal file
61
.eslintrc.json
Normal file
@ -0,0 +1,61 @@
|
||||
{
|
||||
"env": {
|
||||
"es6": true,
|
||||
"jasmine": true,
|
||||
"node": true,
|
||||
"worker": true
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018
|
||||
},
|
||||
"plugins": ["@typescript-eslint", "prettier", "simple-import-sort", "import"],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier/@typescript-eslint",
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:import/typescript"
|
||||
],
|
||||
"rules": {
|
||||
"curly": ["warn", "multi-line", "consistent"],
|
||||
"no-console": ["warn", { "allow": ["error", "info", "warn"] }],
|
||||
"no-param-reassign": "warn",
|
||||
"no-shadow": "warn",
|
||||
"prefer-const": "warn",
|
||||
"spaced-comment": ["warn", "always", { "line": { "markers": ["/ <reference"] } }],
|
||||
"import/no-cycle": "warn",
|
||||
"simple-import-sort/sort": "warn",
|
||||
"@typescript-eslint/explicit-function-return-type": ["warn", { "allowExpressions": true }],
|
||||
"@typescript-eslint/no-empty-interface": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }],
|
||||
"@typescript-eslint/no-use-before-define": "warn"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": "**/*.js",
|
||||
"rules": {
|
||||
"@typescript-eslint/no-var-requires": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "**/*.spec.ts",
|
||||
"rules": {
|
||||
"@typescript-eslint/no-non-null-assertion": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "jasmine-testrunner.js",
|
||||
"rules": {
|
||||
"@typescript-eslint/camelcase": ["error", { "properties": "never" }]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "packages/{iov-bns,iov-tendermint-rpc}/**/*.ts",
|
||||
"rules": {
|
||||
"@typescript-eslint/camelcase": ["error", { "allow": ["v0_[0-9]+"] }]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
4
.prettierrc.json
Normal file
4
.prettierrc.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"printWidth": 110,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
@ -9,6 +9,7 @@ import {
|
||||
SignableBytes,
|
||||
SignedTransaction,
|
||||
SigningJob,
|
||||
TokenTicker,
|
||||
TransactionId,
|
||||
TxCodec,
|
||||
UnsignedTransaction,
|
||||
@ -17,10 +18,11 @@ import { Sha256 } from "@iov/crypto";
|
||||
import { Encoding } from "@iov/encoding";
|
||||
import { marshalTx, unmarshalTx } from "@tendermint/amino-js";
|
||||
|
||||
import { isValidAddress, pubkeyToAddress } from "./address";
|
||||
import { isValidAddress, pubkeyToAddress, CosmosBech32Prefix } from "./address";
|
||||
import { Caip5 } from "./caip5";
|
||||
import { parseTx } from "./decode";
|
||||
import { buildSignedTx, buildUnsignedTx } from "./encode";
|
||||
import { TokenInfos } from "./types";
|
||||
|
||||
const { toHex, toUtf8 } = Encoding;
|
||||
|
||||
@ -43,10 +45,18 @@ function sortJson(json: any): any {
|
||||
}
|
||||
|
||||
export class CosmosCodec implements TxCodec {
|
||||
private readonly prefix: CosmosBech32Prefix;
|
||||
private readonly tokens: TokenInfos;
|
||||
|
||||
public constructor(prefix: CosmosBech32Prefix, tokens: TokenInfos) {
|
||||
this.prefix = prefix;
|
||||
this.tokens = tokens;
|
||||
}
|
||||
|
||||
public bytesToSign(unsigned: UnsignedTransaction, nonce: Nonce): SigningJob {
|
||||
const accountNumber = 0;
|
||||
const memo = (unsigned as any).memo;
|
||||
const built = buildUnsignedTx(unsigned);
|
||||
const built = buildUnsignedTx(unsigned, this.tokens);
|
||||
|
||||
const signMsg = sortJson({
|
||||
account_number: accountNumber.toString(),
|
||||
@ -65,7 +75,7 @@ export class CosmosCodec implements TxCodec {
|
||||
}
|
||||
|
||||
public bytesToPost(signed: SignedTransaction): PostableBytes {
|
||||
const built = buildSignedTx(signed);
|
||||
const built = buildSignedTx(signed, this.tokens);
|
||||
const bytes = marshalTx(built, true);
|
||||
return bytes as PostableBytes;
|
||||
}
|
||||
@ -81,12 +91,12 @@ export class CosmosCodec implements TxCodec {
|
||||
throw new Error("Nonce is required");
|
||||
}
|
||||
const parsed = unmarshalTx(bytes);
|
||||
return parseTx(parsed, chainId, nonce);
|
||||
// TODO: this needs access to token list
|
||||
return parseTx(parsed, chainId, nonce, this.tokens);
|
||||
}
|
||||
|
||||
public identityToAddress(identity: Identity): Address {
|
||||
const prefix = "cosmos";
|
||||
return pubkeyToAddress(identity.pubkey, prefix);
|
||||
return pubkeyToAddress(identity.pubkey, this.prefix);
|
||||
}
|
||||
|
||||
public isValidAddress(address: string): boolean {
|
||||
@ -94,4 +104,15 @@ export class CosmosCodec implements TxCodec {
|
||||
}
|
||||
}
|
||||
|
||||
export const cosmosCodec = new CosmosCodec();
|
||||
const defaultPrefix = "cosmos" as CosmosBech32Prefix;
|
||||
|
||||
const defaultTokens: TokenInfos = [
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
tokenName: "Atom (Cosmos Hub)",
|
||||
tokenTicker: "ATOM" as TokenTicker,
|
||||
denom: "uatom",
|
||||
},
|
||||
];
|
||||
|
||||
export const cosmosCodec = new CosmosCodec(defaultPrefix, defaultTokens);
|
||||
|
||||
@ -14,8 +14,10 @@ import { Secp256k1 } from "@iov/crypto";
|
||||
import { Encoding } from "@iov/encoding";
|
||||
import { HdPaths, Secp256k1HdWallet, UserProfile } from "@iov/keycontrol";
|
||||
|
||||
import { cosmosCodec } from "./cosmoscodec";
|
||||
import { cosmosCodec, CosmosCodec } from "./cosmoscodec";
|
||||
import { CosmosConnection } from "./cosmosconnection";
|
||||
import { CosmosBech32Prefix } from "./address";
|
||||
import { TokenInfos } from "./types";
|
||||
|
||||
const { fromBase64, toHex } = Encoding;
|
||||
|
||||
@ -26,7 +28,7 @@ function pendingWithoutCosmos(): void {
|
||||
}
|
||||
|
||||
describe("CosmosConnection", () => {
|
||||
const atom = "ATOM" as TokenTicker;
|
||||
const cosm = "COSM" as TokenTicker;
|
||||
const httpUrl = "http://localhost:1317";
|
||||
const defaultChainId = "cosmos:testing" as ChainId;
|
||||
const defaultEmptyAddress = "cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r" as Address;
|
||||
@ -40,10 +42,28 @@ describe("CosmosConnection", () => {
|
||||
const faucetPath = HdPaths.cosmos(0);
|
||||
const defaultRecipient = "cosmos1t70qnpr0az8tf7py83m4ue5y89w58lkjmx0yq2" as Address;
|
||||
|
||||
const defaultPrefix = "cosmos" as CosmosBech32Prefix;
|
||||
|
||||
// this is for wasmd blockchain
|
||||
const defaultTokens: TokenInfos = [
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
tokenName: "Fee Token",
|
||||
tokenTicker: "COSM" as TokenTicker,
|
||||
denom: "cosm",
|
||||
},
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
tokenName: "Staking Token",
|
||||
tokenTicker: "STAKE" as TokenTicker,
|
||||
denom: "stake",
|
||||
},
|
||||
];
|
||||
|
||||
describe("establish", () => {
|
||||
it("can connect to Cosmos via http", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmosConnection.establish(httpUrl);
|
||||
const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
expect(connection).toBeTruthy();
|
||||
connection.disconnect();
|
||||
});
|
||||
@ -52,7 +72,7 @@ describe("CosmosConnection", () => {
|
||||
describe("chainId", () => {
|
||||
it("displays the chain ID", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmosConnection.establish(httpUrl);
|
||||
const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const chainId = connection.chainId();
|
||||
expect(chainId).toEqual(defaultChainId);
|
||||
connection.disconnect();
|
||||
@ -62,7 +82,7 @@ describe("CosmosConnection", () => {
|
||||
describe("height", () => {
|
||||
it("displays the current height", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmosConnection.establish(httpUrl);
|
||||
const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const height = await connection.height();
|
||||
expect(height).toBeGreaterThan(0);
|
||||
connection.disconnect();
|
||||
@ -72,19 +92,19 @@ describe("CosmosConnection", () => {
|
||||
describe("getToken", () => {
|
||||
it("displays a given token", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmosConnection.establish(httpUrl);
|
||||
const token = await connection.getToken("cosm" as TokenTicker);
|
||||
const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const token = await connection.getToken("COSM" as TokenTicker);
|
||||
expect(token).toEqual({
|
||||
fractionalDigits: 6,
|
||||
tokenName: "Cosm",
|
||||
tokenTicker: "cosm" as TokenTicker,
|
||||
tokenName: "Fee Token",
|
||||
tokenTicker: "COSM" as TokenTicker,
|
||||
});
|
||||
connection.disconnect();
|
||||
});
|
||||
|
||||
it("resolves to undefined if the token is not supported", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmosConnection.establish(httpUrl);
|
||||
const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const token = await connection.getToken("whatever" as TokenTicker);
|
||||
expect(token).toBeUndefined();
|
||||
connection.disconnect();
|
||||
@ -94,20 +114,20 @@ describe("CosmosConnection", () => {
|
||||
describe("getAllTokens", () => {
|
||||
it("resolves to a list of all supported tokens", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmosConnection.establish(httpUrl);
|
||||
const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const tokens = await connection.getAllTokens();
|
||||
// TODO: make this more flexible
|
||||
expect(tokens).toEqual([
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
tokenName: "Cosm",
|
||||
tokenTicker: "cosm" as TokenTicker,
|
||||
tokenName: "Fee Token",
|
||||
tokenTicker: "COSM" as TokenTicker,
|
||||
},
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
tokenName: "Stake",
|
||||
tokenTicker: "stake" as TokenTicker,
|
||||
}
|
||||
tokenName: "Staking Token",
|
||||
tokenTicker: "STAKE" as TokenTicker,
|
||||
},
|
||||
]);
|
||||
connection.disconnect();
|
||||
});
|
||||
@ -116,7 +136,7 @@ describe("CosmosConnection", () => {
|
||||
describe("getAccount", () => {
|
||||
it("gets an empty account by address", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmosConnection.establish(httpUrl);
|
||||
const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const account = await connection.getAccount({ address: defaultEmptyAddress });
|
||||
expect(account).toBeUndefined();
|
||||
connection.disconnect();
|
||||
@ -124,32 +144,31 @@ describe("CosmosConnection", () => {
|
||||
|
||||
it("gets an account by address", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmosConnection.establish(httpUrl);
|
||||
const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const account = await connection.getAccount({ address: defaultAddress });
|
||||
if (account === undefined) {
|
||||
throw new Error("Expected account not to be undefined");
|
||||
}
|
||||
expect(account.address).toEqual(defaultAddress);
|
||||
expect(account.pubkey).toEqual(defaultPubkey);
|
||||
// Unsupported coins are filtered out
|
||||
expect(account.balance.length).toEqual(1);
|
||||
// Undefined until we sign a transaction
|
||||
expect(account.pubkey).toEqual(undefined);
|
||||
// Starts with two tokens
|
||||
expect(account.balance.length).toEqual(2);
|
||||
connection.disconnect();
|
||||
});
|
||||
|
||||
it("gets an account by pubkey", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmosConnection.establish(httpUrl);
|
||||
const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const account = await connection.getAccount({ pubkey: defaultPubkey });
|
||||
if (account === undefined) {
|
||||
throw new Error("Expected account not to be undefined");
|
||||
}
|
||||
expect(account.address).toEqual(defaultAddress);
|
||||
expect(account.pubkey).toEqual({
|
||||
algo: Algorithm.Secp256k1,
|
||||
data: fromBase64("A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ"),
|
||||
});
|
||||
// Unsupported coins are filtered out
|
||||
expect(account.balance.length).toEqual(1);
|
||||
// Undefined until we sign a transaction
|
||||
expect(account.pubkey).toEqual(undefined);
|
||||
// Starts with two tokens
|
||||
expect(account.balance.length).toEqual(2);
|
||||
connection.disconnect();
|
||||
});
|
||||
});
|
||||
@ -157,7 +176,7 @@ describe("CosmosConnection", () => {
|
||||
describe("integration tests", () => {
|
||||
it("can post and get a transaction", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmosConnection.establish(httpUrl);
|
||||
const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const profile = new UserProfile();
|
||||
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(faucetMnemonic));
|
||||
const faucet = await profile.createIdentity(wallet.id, defaultChainId, faucetPath);
|
||||
@ -172,13 +191,17 @@ describe("CosmosConnection", () => {
|
||||
amount: {
|
||||
quantity: "75000",
|
||||
fractionalDigits: 6,
|
||||
tokenTicker: atom,
|
||||
tokenTicker: cosm,
|
||||
},
|
||||
});
|
||||
const nonce = await connection.getNonce({ address: faucetAddress });
|
||||
const signed = await profile.signTransaction(faucet, unsigned, cosmosCodec, nonce);
|
||||
const postableBytes = cosmosCodec.bytesToPost(signed);
|
||||
// TODO: we need to use custom codecs everywhere
|
||||
const codec = new CosmosCodec(defaultPrefix, defaultTokens);
|
||||
console.log("nonce:", nonce);
|
||||
const signed = await profile.signTransaction(faucet, unsigned, codec, nonce);
|
||||
const postableBytes = codec.bytesToPost(signed);
|
||||
const response = await connection.postTx(postableBytes);
|
||||
console.log(response);
|
||||
const { transactionId } = response;
|
||||
const blockInfo = await response.blockInfo.waitFor(info => !isBlockInfoPending(info));
|
||||
expect(blockInfo.state).toEqual(TransactionState.Succeeded);
|
||||
@ -216,7 +239,7 @@ describe("CosmosConnection", () => {
|
||||
|
||||
it("can post and search for a transaction", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmosConnection.establish(httpUrl);
|
||||
const connection = await CosmosConnection.establish(httpUrl, defaultPrefix, defaultTokens);
|
||||
const profile = new UserProfile();
|
||||
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(faucetMnemonic));
|
||||
const faucet = await profile.createIdentity(wallet.id, defaultChainId, faucetPath);
|
||||
@ -231,13 +254,16 @@ describe("CosmosConnection", () => {
|
||||
amount: {
|
||||
quantity: "75000",
|
||||
fractionalDigits: 6,
|
||||
tokenTicker: atom,
|
||||
tokenTicker: cosm,
|
||||
},
|
||||
});
|
||||
const nonce = await connection.getNonce({ address: faucetAddress });
|
||||
const signed = await profile.signTransaction(faucet, unsigned, cosmosCodec, nonce);
|
||||
const postableBytes = cosmosCodec.bytesToPost(signed);
|
||||
// TODO: we need to use custom codecs everywhere
|
||||
const codec = new CosmosCodec(defaultPrefix, defaultTokens);
|
||||
const signed = await profile.signTransaction(faucet, unsigned, codec, nonce);
|
||||
const postableBytes = codec.bytesToPost(signed);
|
||||
const response = await connection.postTx(postableBytes);
|
||||
console.log(response);
|
||||
const { transactionId } = response;
|
||||
const blockInfo = await response.blockInfo.waitFor(info => !isBlockInfoPending(info));
|
||||
expect(blockInfo.state).toEqual(TransactionState.Succeeded);
|
||||
|
||||
@ -38,6 +38,7 @@ import { CosmosBech32Prefix, pubkeyToAddress } from "./address";
|
||||
import { Caip5 } from "./caip5";
|
||||
import { decodeAmount, parseTxsResponse } from "./decode";
|
||||
import { RestClient, TxsResponse } from "./restclient";
|
||||
import { TokenInfos } from "./types";
|
||||
|
||||
const { fromBase64 } = Encoding;
|
||||
|
||||
@ -70,10 +71,15 @@ function buildQueryString({
|
||||
}
|
||||
|
||||
export class CosmosConnection implements BlockchainConnection {
|
||||
public static async establish(url: string): Promise<CosmosConnection> {
|
||||
// we must know prefix and tokens a priori to understand the chain
|
||||
public static async establish(
|
||||
url: string,
|
||||
prefix: CosmosBech32Prefix,
|
||||
tokenInfo: TokenInfos,
|
||||
): Promise<CosmosConnection> {
|
||||
const restClient = new RestClient(url);
|
||||
const chainData = await this.initialize(restClient);
|
||||
return new CosmosConnection(restClient, chainData);
|
||||
return new CosmosConnection(restClient, chainData, prefix, tokenInfo);
|
||||
}
|
||||
|
||||
private static async initialize(restClient: RestClient): Promise<ChainData> {
|
||||
@ -84,26 +90,34 @@ export class CosmosConnection implements BlockchainConnection {
|
||||
private readonly restClient: RestClient;
|
||||
private readonly chainData: ChainData;
|
||||
private readonly primaryToken: Token;
|
||||
|
||||
// TODO: deprecate this???
|
||||
private readonly supportedTokens: readonly Token[];
|
||||
|
||||
private readonly _prefix: CosmosBech32Prefix;
|
||||
private readonly tokenInfo: TokenInfos;
|
||||
|
||||
private get prefix(): CosmosBech32Prefix {
|
||||
return "cosmos";
|
||||
return this._prefix;
|
||||
}
|
||||
|
||||
private constructor(restClient: RestClient, chainData: ChainData) {
|
||||
private constructor(
|
||||
restClient: RestClient,
|
||||
chainData: ChainData,
|
||||
prefix: CosmosBech32Prefix,
|
||||
tokenInfo: TokenInfos,
|
||||
) {
|
||||
this.restClient = restClient;
|
||||
this.chainData = chainData;
|
||||
// TODO: this is an argument
|
||||
this.primaryToken = {
|
||||
fractionalDigits: 6,
|
||||
tokenName: "Cosm",
|
||||
tokenTicker: "cosm" as TokenTicker,
|
||||
};
|
||||
this.supportedTokens = [this.primaryToken, {
|
||||
fractionalDigits: 6,
|
||||
tokenName: "Stake",
|
||||
tokenTicker: "stake" as TokenTicker,
|
||||
}];
|
||||
this._prefix = prefix;
|
||||
this.tokenInfo = tokenInfo;
|
||||
|
||||
this.supportedTokens = this.tokenInfo.map(info => ({
|
||||
tokenTicker: info.tokenTicker,
|
||||
tokenName: info.tokenName,
|
||||
fractionalDigits: info.fractionalDigits,
|
||||
}));
|
||||
this.primaryToken = this.supportedTokens[0];
|
||||
}
|
||||
|
||||
public disconnect(): void {
|
||||
@ -131,22 +145,23 @@ export class CosmosConnection implements BlockchainConnection {
|
||||
const address = isPubkeyQuery(query) ? pubkeyToAddress(query.pubkey, this.prefix) : query.address;
|
||||
const { result } = await this.restClient.authAccounts(address);
|
||||
const account = result.value;
|
||||
if (!account.address) {
|
||||
return undefined;
|
||||
}
|
||||
const supportedCoins = account.coins.filter(({ denom }) =>
|
||||
this.supportedTokens.find(
|
||||
// TODO: ugly special case - fix this
|
||||
({ tokenTicker }) => (tokenTicker === "ATOM" && denom === "uatom") || tokenTicker === denom,
|
||||
),
|
||||
this.tokenInfo.find(token => token.denom === denom),
|
||||
);
|
||||
return account.public_key === null
|
||||
const pubkey = !account.public_key
|
||||
? undefined
|
||||
: {
|
||||
address: address,
|
||||
balance: supportedCoins.map(decodeAmount),
|
||||
pubkey: {
|
||||
algo: Algorithm.Secp256k1,
|
||||
data: fromBase64(account.public_key.value) as PubkeyBytes,
|
||||
},
|
||||
algo: Algorithm.Secp256k1,
|
||||
data: fromBase64(account.public_key.value) as PubkeyBytes,
|
||||
};
|
||||
return {
|
||||
address: address,
|
||||
balance: supportedCoins.map(decodeAmount(this.tokenInfo)),
|
||||
pubkey: pubkey,
|
||||
};
|
||||
}
|
||||
|
||||
public watchAccount(_account: AccountQuery): Stream<Account | undefined> {
|
||||
@ -199,6 +214,7 @@ export class CosmosConnection implements BlockchainConnection {
|
||||
}
|
||||
|
||||
public async postTx(tx: PostableBytes): Promise<PostTxResponse> {
|
||||
// TODO: we need to check errors here... bad chain-id breaks this
|
||||
const { txhash, raw_log } = await this.restClient.postTx(tx);
|
||||
const transactionId = txhash as TransactionId;
|
||||
const firstEvent: BlockInfo = { state: TransactionState.Pending };
|
||||
@ -286,6 +302,6 @@ export class CosmosConnection implements BlockchainConnection {
|
||||
const sender = (response.tx.value as any).msg[0].value.from_address;
|
||||
const accountForHeight = await this.restClient.authAccounts(sender, response.height);
|
||||
const nonce = (parseInt(accountForHeight.result.value.sequence, 10) - 1) as Nonce;
|
||||
return parseTxsResponse(chainId, parseInt(response.height, 10), nonce, response);
|
||||
return parseTxsResponse(chainId, parseInt(response.height, 10), nonce, response, this.tokenInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,16 +2,20 @@ import { ChainConnector, ChainId } from "@iov/bcp";
|
||||
|
||||
import { cosmosCodec } from "./cosmoscodec";
|
||||
import { CosmosConnection } from "./cosmosconnection";
|
||||
import { CosmosBech32Prefix } from "./address";
|
||||
import { TokenInfos } from "./types";
|
||||
|
||||
/**
|
||||
* A helper to connect to a cosmos-based chain at a given url
|
||||
*/
|
||||
export function createCosmosConnector(
|
||||
url: string,
|
||||
prefix: CosmosBech32Prefix,
|
||||
tokenInfo: TokenInfos,
|
||||
expectedChainId?: ChainId,
|
||||
): ChainConnector<CosmosConnection> {
|
||||
return {
|
||||
establishConnection: async () => CosmosConnection.establish(url),
|
||||
establishConnection: async () => CosmosConnection.establish(url, prefix, tokenInfo),
|
||||
codec: cosmosCodec,
|
||||
expectedChainId: expectedChainId,
|
||||
};
|
||||
|
||||
@ -15,6 +15,7 @@ import {
|
||||
} from "./decode";
|
||||
import { chainId, nonce, signedTxJson, txId } from "./testdata.spec";
|
||||
import data from "./testdata/cosmoshub.json";
|
||||
import { TokenInfos } from "./types";
|
||||
|
||||
const { fromBase64 } = Encoding;
|
||||
|
||||
@ -51,6 +52,14 @@ describe("decode", () => {
|
||||
},
|
||||
gasLimit: "200000",
|
||||
};
|
||||
const defaultTokens: TokenInfos = [
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
tokenName: "Atom (Cosmos Hub)",
|
||||
tokenTicker: "ATOM" as TokenTicker,
|
||||
denom: "uatom",
|
||||
},
|
||||
];
|
||||
|
||||
describe("decodePubkey", () => {
|
||||
it("works", () => {
|
||||
@ -89,7 +98,7 @@ describe("decode", () => {
|
||||
denom: "uatom",
|
||||
amount: "11657995",
|
||||
};
|
||||
expect(decodeAmount(amount)).toEqual(defaultAmount);
|
||||
expect(decodeAmount(defaultTokens)(amount)).toEqual(defaultAmount);
|
||||
});
|
||||
});
|
||||
|
||||
@ -108,7 +117,7 @@ describe("decode", () => {
|
||||
],
|
||||
},
|
||||
};
|
||||
expect(parseMsg(msg, chainId)).toEqual(defaultSendTransaction);
|
||||
expect(parseMsg(msg, chainId, defaultTokens)).toEqual(defaultSendTransaction);
|
||||
});
|
||||
});
|
||||
|
||||
@ -123,13 +132,13 @@ describe("decode", () => {
|
||||
],
|
||||
gas: "200000",
|
||||
};
|
||||
expect(parseFee(fee)).toEqual(defaultFee);
|
||||
expect(parseFee(fee, defaultTokens)).toEqual(defaultFee);
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseTx", () => {
|
||||
it("works", () => {
|
||||
expect(parseTx(data.tx, chainId, nonce)).toEqual(signedTxJson);
|
||||
expect(parseTx(data.tx, chainId, nonce, defaultTokens)).toEqual(signedTxJson);
|
||||
});
|
||||
});
|
||||
|
||||
@ -149,7 +158,7 @@ describe("decode", () => {
|
||||
transactionId: txId,
|
||||
log: '[{"msg_index":0,"success":true,"log":""}]',
|
||||
};
|
||||
expect(parseTxsResponse(chainId, currentHeight, nonce, txsResponse)).toEqual(expected);
|
||||
expect(parseTxsResponse(chainId, currentHeight, nonce, txsResponse, defaultTokens)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -20,7 +20,7 @@ import { Encoding } from "@iov/encoding";
|
||||
import amino from "@tendermint/amino-js";
|
||||
|
||||
import { TxsResponse } from "./restclient";
|
||||
import { isAminoStdTx } from "./types";
|
||||
import { isAminoStdTx, TokenInfos, coinToAmount } from "./types";
|
||||
|
||||
const { fromBase64 } = Encoding;
|
||||
|
||||
@ -45,20 +45,15 @@ export function decodeFullSignature(signature: amino.StdSignature, nonce: number
|
||||
};
|
||||
}
|
||||
|
||||
export function decodeAmount(amount: amino.Coin): Amount {
|
||||
// TODO: more uglyness here (breaks unit tests)
|
||||
if (amount.denom !== "uatom") {
|
||||
throw new Error("Only ATOM amounts are supported");
|
||||
}
|
||||
return {
|
||||
fractionalDigits: 6,
|
||||
quantity: amount.amount,
|
||||
tokenTicker: atom,
|
||||
// tokenTicker: amount.denom as TokenTicker,
|
||||
};
|
||||
}
|
||||
// TODO: this needs access to token list - we need something more like amountToCoin and coinToAmount here
|
||||
// and wire that info all the way from both connection and codec.
|
||||
|
||||
export function parseMsg(msg: amino.Msg, chainId: ChainId): SendTransaction {
|
||||
// TODO: return null vs throw exception for undefined???
|
||||
export const decodeAmount = (tokens: TokenInfos) => (coin: amino.Coin): Amount => {
|
||||
return coinToAmount(tokens, coin);
|
||||
};
|
||||
|
||||
export function parseMsg(msg: amino.Msg, chainId: ChainId, tokens: TokenInfos): SendTransaction {
|
||||
if (msg.type !== "cosmos-sdk/MsgSend") {
|
||||
throw new Error("Unknown message type in transaction");
|
||||
}
|
||||
@ -74,21 +69,22 @@ export function parseMsg(msg: amino.Msg, chainId: ChainId): SendTransaction {
|
||||
chainId: chainId,
|
||||
sender: msgValue.from_address as Address,
|
||||
recipient: msgValue.to_address as Address,
|
||||
amount: decodeAmount(msgValue.amount[0]),
|
||||
// TODO: this needs access to token list
|
||||
amount: decodeAmount(tokens)(msgValue.amount[0]),
|
||||
};
|
||||
}
|
||||
|
||||
export function parseFee(fee: amino.StdFee): Fee {
|
||||
export function parseFee(fee: amino.StdFee, tokens: TokenInfos): Fee {
|
||||
if (fee.amount.length !== 1) {
|
||||
throw new Error("Only fee with one amount is supported");
|
||||
}
|
||||
return {
|
||||
tokens: decodeAmount(fee.amount[0]),
|
||||
tokens: decodeAmount(tokens)(fee.amount[0]),
|
||||
gasLimit: fee.gas,
|
||||
};
|
||||
}
|
||||
|
||||
export function parseTx(tx: amino.Tx, chainId: ChainId, nonce: Nonce): SignedTransaction {
|
||||
export function parseTx(tx: amino.Tx, chainId: ChainId, nonce: Nonce, tokens: TokenInfos): SignedTransaction {
|
||||
const txValue = tx.value;
|
||||
if (!isAminoStdTx(txValue)) {
|
||||
throw new Error("Only Amino StdTx is supported");
|
||||
@ -98,8 +94,10 @@ export function parseTx(tx: amino.Tx, chainId: ChainId, nonce: Nonce): SignedTra
|
||||
}
|
||||
|
||||
const [primarySignature] = txValue.signatures.map(signature => decodeFullSignature(signature, nonce));
|
||||
const msg = parseMsg(txValue.msg[0], chainId);
|
||||
const fee = parseFee(txValue.fee);
|
||||
// TODO: this needs access to token list
|
||||
const msg = parseMsg(txValue.msg[0], chainId, tokens);
|
||||
// TODO: this needs access to token list
|
||||
const fee = parseFee(txValue.fee, tokens);
|
||||
|
||||
const transaction = {
|
||||
...msg,
|
||||
@ -119,10 +117,11 @@ export function parseTxsResponse(
|
||||
currentHeight: number,
|
||||
nonce: Nonce,
|
||||
response: TxsResponse,
|
||||
tokens: TokenInfos,
|
||||
): ConfirmedAndSignedTransaction<UnsignedTransaction> {
|
||||
const height = parseInt(response.height, 10);
|
||||
return {
|
||||
...parseTx(response.tx, chainId, nonce),
|
||||
...parseTx(response.tx, chainId, nonce, tokens),
|
||||
height: height,
|
||||
confirmations: currentHeight - height + 1,
|
||||
transactionId: response.txhash as TransactionId,
|
||||
|
||||
@ -21,6 +21,7 @@ import {
|
||||
encodeFullSignature,
|
||||
encodePubkey,
|
||||
} from "./encode";
|
||||
import { TokenInfos } from "./types";
|
||||
|
||||
const { fromBase64 } = Encoding;
|
||||
|
||||
@ -40,6 +41,14 @@ describe("encode", () => {
|
||||
tokenTicker: atom,
|
||||
};
|
||||
const defaultMemo = "hello cosmos hub";
|
||||
const defaultTokens: TokenInfos = [
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
tokenName: "Atom (Cosmos Hub)",
|
||||
tokenTicker: "ATOM" as TokenTicker,
|
||||
denom: "uatom",
|
||||
},
|
||||
];
|
||||
|
||||
describe("encodePubKey", () => {
|
||||
it("encodes a Secp256k1 pubkey", () => {
|
||||
@ -52,7 +61,7 @@ describe("encode", () => {
|
||||
|
||||
describe("encodeAmount", () => {
|
||||
it("encodes an amount", () => {
|
||||
expect(encodeAmount(defaultAmount)).toEqual({
|
||||
expect(encodeAmount(defaultAmount, defaultTokens)).toEqual({
|
||||
denom: "uatom",
|
||||
amount: "11657995",
|
||||
});
|
||||
@ -64,7 +73,7 @@ describe("encode", () => {
|
||||
const fee = {
|
||||
gasLimit: "200000",
|
||||
};
|
||||
expect(() => encodeFee(fee)).toThrowError(/cannot encode fee without tokens/i);
|
||||
expect(() => encodeFee(fee, defaultTokens)).toThrowError(/cannot encode fee without tokens/i);
|
||||
});
|
||||
|
||||
it("throws without gas limit", () => {
|
||||
@ -75,7 +84,7 @@ describe("encode", () => {
|
||||
tokenTicker: atom,
|
||||
},
|
||||
};
|
||||
expect(() => encodeFee(fee)).toThrowError(/cannot encode fee without gas limit/i);
|
||||
expect(() => encodeFee(fee, defaultTokens)).toThrowError(/cannot encode fee without gas limit/i);
|
||||
});
|
||||
|
||||
it("encodes a fee", () => {
|
||||
@ -87,7 +96,7 @@ describe("encode", () => {
|
||||
},
|
||||
gasLimit: "200000",
|
||||
};
|
||||
expect(encodeFee(fee)).toEqual({
|
||||
expect(encodeFee(fee, defaultTokens)).toEqual({
|
||||
amount: [{ denom: "uatom", amount: "5000" }],
|
||||
gas: "200000",
|
||||
});
|
||||
@ -168,7 +177,9 @@ describe("encode", () => {
|
||||
chainId: defaultChainId,
|
||||
escrowId: "defg",
|
||||
};
|
||||
expect(() => buildUnsignedTx(tx)).toThrowError(/received transaction of unsupported kind/i);
|
||||
expect(() => buildUnsignedTx(tx, defaultTokens)).toThrowError(
|
||||
/received transaction of unsupported kind/i,
|
||||
);
|
||||
});
|
||||
|
||||
it("builds a send transaction without fee", () => {
|
||||
@ -180,7 +191,7 @@ describe("encode", () => {
|
||||
recipient: defaultRecipient,
|
||||
memo: defaultMemo,
|
||||
};
|
||||
expect(buildUnsignedTx(tx)).toEqual({
|
||||
expect(buildUnsignedTx(tx, defaultTokens)).toEqual({
|
||||
type: "cosmos-sdk/StdTx",
|
||||
value: {
|
||||
msg: [
|
||||
@ -225,7 +236,7 @@ describe("encode", () => {
|
||||
gasLimit: "200000",
|
||||
},
|
||||
};
|
||||
expect(buildUnsignedTx(tx)).toEqual({
|
||||
expect(buildUnsignedTx(tx, defaultTokens)).toEqual({
|
||||
type: "cosmos-sdk/StdTx",
|
||||
value: {
|
||||
msg: [
|
||||
@ -286,7 +297,7 @@ describe("encode", () => {
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(buildSignedTx(tx)).toEqual({
|
||||
expect(buildSignedTx(tx, defaultTokens)).toEqual({
|
||||
type: "cosmos-sdk/StdTx",
|
||||
value: {
|
||||
msg: [
|
||||
|
||||
@ -13,7 +13,7 @@ import { Secp256k1 } from "@iov/crypto";
|
||||
import { Encoding } from "@iov/encoding";
|
||||
import amino from "@tendermint/amino-js";
|
||||
|
||||
import { AminoTx } from "./types";
|
||||
import { AminoTx, TokenInfos, amountToCoin } from "./types";
|
||||
|
||||
const { toBase64 } = Encoding;
|
||||
|
||||
@ -34,17 +34,11 @@ export function encodePubkey(pubkey: PubkeyBundle): amino.PubKey {
|
||||
}
|
||||
}
|
||||
|
||||
export function encodeAmount(amount: Amount): amino.Coin {
|
||||
if (amount.tokenTicker !== "ATOM") {
|
||||
throw new Error("Only ATOM amounts are supported");
|
||||
}
|
||||
return {
|
||||
denom: "uatom",
|
||||
amount: amount.quantity,
|
||||
};
|
||||
export function encodeAmount(amount: Amount, tokens: TokenInfos): amino.Coin {
|
||||
return amountToCoin(tokens, amount);
|
||||
}
|
||||
|
||||
export function encodeFee(fee: Fee): amino.StdFee {
|
||||
export function encodeFee(fee: Fee, tokens: TokenInfos): amino.StdFee {
|
||||
if (fee.tokens === undefined) {
|
||||
throw new Error("Cannot encode fee without tokens");
|
||||
}
|
||||
@ -52,7 +46,7 @@ export function encodeFee(fee: Fee): amino.StdFee {
|
||||
throw new Error("Cannot encode fee without gas limit");
|
||||
}
|
||||
return {
|
||||
amount: [encodeAmount(fee.tokens)],
|
||||
amount: [encodeAmount(fee.tokens, tokens)],
|
||||
gas: fee.gasLimit,
|
||||
};
|
||||
}
|
||||
@ -68,7 +62,7 @@ export function encodeFullSignature(fullSignature: FullSignature): amino.StdSign
|
||||
};
|
||||
}
|
||||
|
||||
export function buildUnsignedTx(tx: UnsignedTransaction): AminoTx {
|
||||
export function buildUnsignedTx(tx: UnsignedTransaction, tokens: TokenInfos): AminoTx {
|
||||
if (!isSendTransaction(tx)) {
|
||||
throw new Error("Received transaction of unsupported kind");
|
||||
}
|
||||
@ -81,14 +75,14 @@ export function buildUnsignedTx(tx: UnsignedTransaction): AminoTx {
|
||||
value: {
|
||||
from_address: tx.sender,
|
||||
to_address: tx.recipient,
|
||||
amount: [encodeAmount(tx.amount)],
|
||||
amount: [encodeAmount(tx.amount, tokens)],
|
||||
},
|
||||
},
|
||||
],
|
||||
memo: tx.memo || "",
|
||||
signatures: [],
|
||||
fee: tx.fee
|
||||
? encodeFee(tx.fee)
|
||||
? encodeFee(tx.fee, tokens)
|
||||
: {
|
||||
amount: [],
|
||||
gas: "",
|
||||
@ -97,8 +91,8 @@ export function buildUnsignedTx(tx: UnsignedTransaction): AminoTx {
|
||||
};
|
||||
}
|
||||
|
||||
export function buildSignedTx(tx: SignedTransaction): AminoTx {
|
||||
const built = buildUnsignedTx(tx.transaction);
|
||||
export function buildSignedTx(tx: SignedTransaction, tokens: TokenInfos): AminoTx {
|
||||
const built = buildUnsignedTx(tx.transaction, tokens);
|
||||
return {
|
||||
...built,
|
||||
value: {
|
||||
|
||||
31
src/types.ts
31
src/types.ts
@ -1,3 +1,4 @@
|
||||
import { Amount, Token } from "@iov/bcp";
|
||||
import amino from "@tendermint/amino-js";
|
||||
|
||||
export type AminoTx = amino.Tx & { readonly value: amino.StdTx };
|
||||
@ -8,3 +9,33 @@ export function isAminoStdTx(txValue: amino.TxValue): txValue is amino.StdTx {
|
||||
typeof memo === "string" && Array.isArray(msg) && typeof fee === "object" && Array.isArray(signatures)
|
||||
);
|
||||
}
|
||||
|
||||
export interface TokenInfo extends Token {
|
||||
readonly denom: string;
|
||||
}
|
||||
|
||||
export type TokenInfos = ReadonlyArray<TokenInfo>;
|
||||
|
||||
// TODO: alias amino types
|
||||
export function amountToCoin(lookup: ReadonlyArray<TokenInfo>, amount: Amount): amino.Coin {
|
||||
const match = lookup.find(({ tokenTicker }) => tokenTicker === amount.tokenTicker);
|
||||
if (!match) {
|
||||
throw Error(`unknown ticker: ${amount.tokenTicker}`);
|
||||
}
|
||||
return {
|
||||
denom: match.denom,
|
||||
amount: amount.quantity,
|
||||
};
|
||||
}
|
||||
|
||||
export function coinToAmount(tokens: TokenInfos, coin: amino.Coin): Amount {
|
||||
const match = tokens.find(({ denom }) => denom === coin.denom);
|
||||
if (!match) {
|
||||
throw Error(`unknown denom: ${coin.denom}`);
|
||||
}
|
||||
return {
|
||||
tokenTicker: match.tokenTicker,
|
||||
fractionalDigits: match.fractionalDigits,
|
||||
quantity: coin.amount,
|
||||
};
|
||||
}
|
||||
|
||||
21
types/address.d.ts
vendored
21
types/address.d.ts
vendored
@ -1,23 +1,12 @@
|
||||
import { Address, PubkeyBundle } from "@iov/bcp";
|
||||
export declare type CosmosAddressBech32Prefix =
|
||||
| "cosmos"
|
||||
| "cosmosvalcons"
|
||||
| "cosmosvaloper";
|
||||
export declare type CosmosPubkeyBech32Prefix =
|
||||
| "cosmospub"
|
||||
| "cosmosvalconspub"
|
||||
| "cosmosvaloperpub";
|
||||
export declare type CosmosBech32Prefix =
|
||||
| CosmosAddressBech32Prefix
|
||||
| CosmosPubkeyBech32Prefix;
|
||||
export declare type CosmosAddressBech32Prefix = "cosmos" | "cosmosvalcons" | "cosmosvaloper";
|
||||
export declare type CosmosPubkeyBech32Prefix = "cosmospub" | "cosmosvalconspub" | "cosmosvaloperpub";
|
||||
export declare type CosmosBech32Prefix = CosmosAddressBech32Prefix | CosmosPubkeyBech32Prefix;
|
||||
export declare function decodeCosmosAddress(
|
||||
address: Address
|
||||
address: Address,
|
||||
): {
|
||||
readonly prefix: CosmosAddressBech32Prefix;
|
||||
readonly data: Uint8Array;
|
||||
};
|
||||
export declare function isValidAddress(address: string): boolean;
|
||||
export declare function pubkeyToAddress(
|
||||
pubkey: PubkeyBundle,
|
||||
prefix: CosmosBech32Prefix
|
||||
): Address;
|
||||
export declare function pubkeyToAddress(pubkey: PubkeyBundle, prefix: CosmosBech32Prefix): Address;
|
||||
|
||||
13
types/cosmoscodec.d.ts
vendored
13
types/cosmoscodec.d.ts
vendored
@ -8,17 +8,18 @@ import {
|
||||
SigningJob,
|
||||
TransactionId,
|
||||
TxCodec,
|
||||
UnsignedTransaction
|
||||
UnsignedTransaction,
|
||||
} from "@iov/bcp";
|
||||
import { CosmosBech32Prefix } from "./address";
|
||||
import { TokenInfos } from "./types";
|
||||
export declare class CosmosCodec implements TxCodec {
|
||||
private readonly prefix;
|
||||
private readonly tokens;
|
||||
constructor(prefix: CosmosBech32Prefix, tokens: TokenInfos);
|
||||
bytesToSign(unsigned: UnsignedTransaction, nonce: Nonce): SigningJob;
|
||||
bytesToPost(signed: SignedTransaction): PostableBytes;
|
||||
identifier(signed: SignedTransaction): TransactionId;
|
||||
parseBytes(
|
||||
bytes: PostableBytes,
|
||||
chainId: ChainId,
|
||||
nonce?: Nonce
|
||||
): SignedTransaction;
|
||||
parseBytes(bytes: PostableBytes, chainId: ChainId, nonce?: Nonce): SignedTransaction;
|
||||
identityToAddress(identity: Identity): Address;
|
||||
isValidAddress(address: string): boolean;
|
||||
}
|
||||
|
||||
33
types/cosmosconnection.d.ts
vendored
33
types/cosmosconnection.d.ts
vendored
@ -17,16 +17,20 @@ import {
|
||||
TokenTicker,
|
||||
TransactionId,
|
||||
TransactionQuery,
|
||||
UnsignedTransaction
|
||||
UnsignedTransaction,
|
||||
} from "@iov/bcp";
|
||||
import { Stream } from "xstream";
|
||||
import { CosmosBech32Prefix } from "./address";
|
||||
import { TokenInfos } from "./types";
|
||||
export declare class CosmosConnection implements BlockchainConnection {
|
||||
static establish(url: string): Promise<CosmosConnection>;
|
||||
static establish(url: string, prefix: CosmosBech32Prefix, tokenInfo: TokenInfos): Promise<CosmosConnection>;
|
||||
private static initialize;
|
||||
private readonly restClient;
|
||||
private readonly chainData;
|
||||
private readonly primaryToken;
|
||||
private readonly supportedTokens;
|
||||
private readonly _prefix;
|
||||
private readonly tokenInfo;
|
||||
private get prefix();
|
||||
private constructor();
|
||||
disconnect(): void;
|
||||
@ -37,29 +41,16 @@ export declare class CosmosConnection implements BlockchainConnection {
|
||||
getAccount(query: AccountQuery): Promise<Account | undefined>;
|
||||
watchAccount(_account: AccountQuery): Stream<Account | undefined>;
|
||||
getNonce(query: AddressQuery | PubkeyQuery): Promise<Nonce>;
|
||||
getNonces(
|
||||
query: AddressQuery | PubkeyQuery,
|
||||
count: number
|
||||
): Promise<readonly Nonce[]>;
|
||||
getNonces(query: AddressQuery | PubkeyQuery, count: number): Promise<readonly Nonce[]>;
|
||||
getBlockHeader(height: number): Promise<BlockHeader>;
|
||||
watchBlockHeaders(): Stream<BlockHeader>;
|
||||
getTx(
|
||||
id: TransactionId
|
||||
): Promise<
|
||||
ConfirmedAndSignedTransaction<UnsignedTransaction> | FailedTransaction
|
||||
>;
|
||||
getTx(id: TransactionId): Promise<ConfirmedAndSignedTransaction<UnsignedTransaction> | FailedTransaction>;
|
||||
postTx(tx: PostableBytes): Promise<PostTxResponse>;
|
||||
searchTx(
|
||||
query: TransactionQuery
|
||||
): Promise<
|
||||
readonly (ConfirmedTransaction<UnsignedTransaction> | FailedTransaction)[]
|
||||
>;
|
||||
listenTx(
|
||||
_query: TransactionQuery
|
||||
): Stream<ConfirmedTransaction<UnsignedTransaction> | FailedTransaction>;
|
||||
liveTx(
|
||||
_query: TransactionQuery
|
||||
): Stream<ConfirmedTransaction<UnsignedTransaction> | FailedTransaction>;
|
||||
query: TransactionQuery,
|
||||
): Promise<readonly (ConfirmedTransaction<UnsignedTransaction> | FailedTransaction)[]>;
|
||||
listenTx(_query: TransactionQuery): Stream<ConfirmedTransaction<UnsignedTransaction> | FailedTransaction>;
|
||||
liveTx(_query: TransactionQuery): Stream<ConfirmedTransaction<UnsignedTransaction> | FailedTransaction>;
|
||||
getFeeQuote(tx: UnsignedTransaction): Promise<Fee>;
|
||||
withDefaultFee<T extends UnsignedTransaction>(tx: T): Promise<T>;
|
||||
private parseAndPopulateTxResponse;
|
||||
|
||||
6
types/cosmosconnector.d.ts
vendored
6
types/cosmosconnector.d.ts
vendored
@ -1,9 +1,13 @@
|
||||
import { ChainConnector, ChainId } from "@iov/bcp";
|
||||
import { CosmosConnection } from "./cosmosconnection";
|
||||
import { CosmosBech32Prefix } from "./address";
|
||||
import { TokenInfos } from "./types";
|
||||
/**
|
||||
* A helper to connect to a cosmos-based chain at a given url
|
||||
*/
|
||||
export declare function createCosmosConnector(
|
||||
url: string,
|
||||
expectedChainId?: ChainId
|
||||
prefix: CosmosBech32Prefix,
|
||||
tokenInfo: TokenInfos,
|
||||
expectedChainId?: ChainId,
|
||||
): ChainConnector<CosmosConnection>;
|
||||
|
||||
23
types/decode.d.ts
vendored
23
types/decode.d.ts
vendored
@ -9,30 +9,27 @@ import {
|
||||
SendTransaction,
|
||||
SignatureBytes,
|
||||
SignedTransaction,
|
||||
UnsignedTransaction
|
||||
UnsignedTransaction,
|
||||
} from "@iov/bcp";
|
||||
import amino from "@tendermint/amino-js";
|
||||
import { TxsResponse } from "./restclient";
|
||||
import { TokenInfos } from "./types";
|
||||
export declare function decodePubkey(pubkey: amino.PubKey): PubkeyBundle;
|
||||
export declare function decodeSignature(signature: string): SignatureBytes;
|
||||
export declare function decodeFullSignature(
|
||||
signature: amino.StdSignature,
|
||||
nonce: number
|
||||
): FullSignature;
|
||||
export declare function decodeAmount(amount: amino.Coin): Amount;
|
||||
export declare function parseMsg(
|
||||
msg: amino.Msg,
|
||||
chainId: ChainId
|
||||
): SendTransaction;
|
||||
export declare function parseFee(fee: amino.StdFee): Fee;
|
||||
export declare function decodeFullSignature(signature: amino.StdSignature, nonce: number): FullSignature;
|
||||
export declare const decodeAmount: (tokens: TokenInfos) => (coin: amino.Coin) => Amount;
|
||||
export declare function parseMsg(msg: amino.Msg, chainId: ChainId, tokens: TokenInfos): SendTransaction;
|
||||
export declare function parseFee(fee: amino.StdFee, tokens: TokenInfos): Fee;
|
||||
export declare function parseTx(
|
||||
tx: amino.Tx,
|
||||
chainId: ChainId,
|
||||
nonce: Nonce
|
||||
nonce: Nonce,
|
||||
tokens: TokenInfos,
|
||||
): SignedTransaction;
|
||||
export declare function parseTxsResponse(
|
||||
chainId: ChainId,
|
||||
currentHeight: number,
|
||||
nonce: Nonce,
|
||||
response: TxsResponse
|
||||
response: TxsResponse,
|
||||
tokens: TokenInfos,
|
||||
): ConfirmedAndSignedTransaction<UnsignedTransaction>;
|
||||
|
||||
23
types/encode.d.ts
vendored
23
types/encode.d.ts
vendored
@ -1,18 +1,9 @@
|
||||
import {
|
||||
Amount,
|
||||
Fee,
|
||||
FullSignature,
|
||||
PubkeyBundle,
|
||||
SignedTransaction,
|
||||
UnsignedTransaction
|
||||
} from "@iov/bcp";
|
||||
import { Amount, Fee, FullSignature, PubkeyBundle, SignedTransaction, UnsignedTransaction } from "@iov/bcp";
|
||||
import amino from "@tendermint/amino-js";
|
||||
import { AminoTx } from "./types";
|
||||
import { AminoTx, TokenInfos } from "./types";
|
||||
export declare function encodePubkey(pubkey: PubkeyBundle): amino.PubKey;
|
||||
export declare function encodeAmount(amount: Amount): amino.Coin;
|
||||
export declare function encodeFee(fee: Fee): amino.StdFee;
|
||||
export declare function encodeFullSignature(
|
||||
fullSignature: FullSignature
|
||||
): amino.StdSignature;
|
||||
export declare function buildUnsignedTx(tx: UnsignedTransaction): AminoTx;
|
||||
export declare function buildSignedTx(tx: SignedTransaction): AminoTx;
|
||||
export declare function encodeAmount(amount: Amount, tokens: TokenInfos): amino.Coin;
|
||||
export declare function encodeFee(fee: Fee, tokens: TokenInfos): amino.StdFee;
|
||||
export declare function encodeFullSignature(fullSignature: FullSignature): amino.StdSignature;
|
||||
export declare function buildUnsignedTx(tx: UnsignedTransaction, tokens: TokenInfos): AminoTx;
|
||||
export declare function buildSignedTx(tx: SignedTransaction, tokens: TokenInfos): AminoTx;
|
||||
|
||||
5
types/restclient.d.ts
vendored
5
types/restclient.d.ts
vendored
@ -69,10 +69,7 @@ export declare class RestClient {
|
||||
nodeInfo(): Promise<NodeInfoResponse>;
|
||||
blocksLatest(): Promise<BlocksResponse>;
|
||||
blocks(height: number): Promise<BlocksResponse>;
|
||||
authAccounts(
|
||||
address: Address,
|
||||
height?: string
|
||||
): Promise<AuthAccountsResponse>;
|
||||
authAccounts(address: Address, height?: string): Promise<AuthAccountsResponse>;
|
||||
txs(query: string): Promise<SearchTxsResponse>;
|
||||
txsById(id: TransactionId): Promise<TxsResponse>;
|
||||
postTx(tx: PostableBytes): Promise<PostTxsResponse>;
|
||||
|
||||
11
types/types.d.ts
vendored
11
types/types.d.ts
vendored
@ -1,7 +1,12 @@
|
||||
import { Amount, Token } from "@iov/bcp";
|
||||
import amino from "@tendermint/amino-js";
|
||||
export declare type AminoTx = amino.Tx & {
|
||||
readonly value: amino.StdTx;
|
||||
};
|
||||
export declare function isAminoStdTx(
|
||||
txValue: amino.TxValue
|
||||
): txValue is amino.StdTx;
|
||||
export declare function isAminoStdTx(txValue: amino.TxValue): txValue is amino.StdTx;
|
||||
export interface TokenInfo extends Token {
|
||||
readonly denom: string;
|
||||
}
|
||||
export declare type TokenInfos = ReadonlyArray<TokenInfo>;
|
||||
export declare function amountToCoin(lookup: ReadonlyArray<TokenInfo>, amount: Amount): amino.Coin;
|
||||
export declare function coinToAmount(tokens: TokenInfos, coin: amino.Coin): Amount;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user