Remove BCP's dependency on @cosmwasm/cosmwasm

This commit is contained in:
Simon Warta 2020-06-02 16:50:47 +02:00
parent 4a350c2409
commit 1e8ed6fc10
21 changed files with 79 additions and 806 deletions

View File

@ -36,7 +36,6 @@
"pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js"
},
"dependencies": {
"@cosmwasm/cosmwasm": "^0.8.0",
"@cosmwasm/sdk38": "^0.8.0",
"@iov/bcp": "^2.1.0",
"@iov/crypto": "^2.1.0",

View File

@ -1,9 +1,9 @@
import { Address, PostableBytes, PrehashType, SendTransaction, TokenTicker } from "@iov/bcp";
import { PostableBytes, PrehashType } from "@iov/bcp";
import { Encoding } from "@iov/encoding";
import { CosmWasmCodec } from "./cosmwasmcodec";
import { chainId, nonce, sendTxJson, signedTxBin, signedTxEncodedJson, signedTxJson } from "./testdata.spec";
import { BankToken, Erc20Token } from "./types";
import { BankToken } from "./types";
const { toUtf8 } = Encoding;
@ -17,26 +17,8 @@ const defaultBankTokens: readonly BankToken[] = [
},
];
const defaultErc20Tokens: readonly Erc20Token[] = [
{
contractAddress: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5",
fractionalDigits: 5,
ticker: "HASH",
},
{
contractAddress: "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd",
fractionalDigits: 0,
ticker: "ISA",
},
{
contractAddress: "cosmos18r5szma8hm93pvx6lwpjwyxruw27e0k5uw835c",
fractionalDigits: 18,
ticker: "JADE",
},
];
describe("CosmWasmCodec", () => {
const codec = new CosmWasmCodec(defaultPrefix, defaultBankTokens, defaultErc20Tokens);
const codec = new CosmWasmCodec(defaultPrefix, defaultBankTokens);
describe("isValidAddress", () => {
it("accepts valid addresses", () => {
@ -65,38 +47,6 @@ describe("CosmWasmCodec", () => {
};
expect(codec.bytesToSign(sendTxJson, nonce)).toEqual(expected);
});
it("works for ERC20 send", () => {
const bashSendTx: SendTransaction = {
kind: "bcp/send",
chainId: chainId,
sender: "cosmos1txqfn5jmcts0x0q7krdxj8tgf98tj0965vqlmq" as Address,
recipient: "cosmos1dddd" as Address,
memo: "My first ISA payment",
amount: {
fractionalDigits: 0,
quantity: "345",
tokenTicker: "ISA" as TokenTicker,
},
fee: {
tokens: {
fractionalDigits: 6,
quantity: "2500",
tokenTicker: "ATOM" as TokenTicker,
},
gasLimit: "100000",
},
};
const expected = {
bytes: toUtf8(
'{"account_number":"0","chain_id":"cosmoshub-3","fee":{"amount":[{"amount":"2500","denom":"uatom"}],"gas":"100000"},"memo":"My first ISA payment","msgs":[{"type":"wasm/execute","value":{"contract":"cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd","msg":{"transfer":{"amount":"345","recipient":"cosmos1dddd"}},"sender":"cosmos1txqfn5jmcts0x0q7krdxj8tgf98tj0965vqlmq","sent_funds":[]}}],"sequence":"99"}',
),
prehashType: PrehashType.Sha256,
};
expect(codec.bytesToSign(bashSendTx, nonce)).toEqual(expected);
});
});
describe("bytesToPost", () => {

View File

@ -20,25 +20,19 @@ import { pubkeyToAddress } from "./address";
import { Caip5 } from "./caip5";
import { parseSignedTx } from "./decode";
import { buildSignedTx, buildUnsignedTx } from "./encode";
import { BankToken, Erc20Token, nonceToAccountNumber, nonceToSequence } from "./types";
import { BankToken, nonceToAccountNumber, nonceToSequence } from "./types";
export class CosmWasmCodec implements TxCodec {
private readonly addressPrefix: string;
private readonly bankTokens: readonly BankToken[];
private readonly erc20Tokens: readonly Erc20Token[];
public constructor(
addressPrefix: string,
bankTokens: readonly BankToken[],
erc20Tokens: readonly Erc20Token[] = [],
) {
public constructor(addressPrefix: string, bankTokens: readonly BankToken[]) {
this.addressPrefix = addressPrefix;
this.bankTokens = bankTokens;
this.erc20Tokens = erc20Tokens;
}
public bytesToSign(unsigned: UnsignedTransaction, nonce: Nonce): SigningJob {
const built = buildUnsignedTx(unsigned, this.bankTokens, this.erc20Tokens);
const built = buildUnsignedTx(unsigned, this.bankTokens);
const signBytes = makeSignBytes(
built.value.msg,
@ -58,7 +52,7 @@ export class CosmWasmCodec implements TxCodec {
// PostableBytes are JSON-encoded StdTx
public bytesToPost(signed: SignedTransaction): PostableBytes {
// TODO: change this as well (return StdTx, not AminoTx)?
const built = buildSignedTx(signed, this.bankTokens, this.erc20Tokens);
const built = buildSignedTx(signed, this.bankTokens);
return marshalTx(built.value) as PostableBytes;
}
@ -76,7 +70,7 @@ export class CosmWasmCodec implements TxCodec {
throw new Error("Nonce is required");
}
const parsed = unmarshalTx(bytes);
return parseSignedTx(parsed, chainId, nonce, this.bankTokens, this.erc20Tokens);
return parseSignedTx(parsed, chainId, nonce, this.bankTokens);
}
public identityToAddress(identity: Identity): Address {

View File

@ -69,7 +69,6 @@ const bob = {
describe("CosmWasmConnection", () => {
const cosm = "COSM" as TokenTicker;
const isa = "ISA" as TokenTicker;
const httpUrl = "http://localhost:1317";
const defaultChainId = "cosmos:testing" as ChainId;
const defaultEmptyAddress = "cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r" as Address;
@ -104,26 +103,6 @@ describe("CosmWasmConnection", () => {
denom: "ustake",
},
],
erc20Tokens: [
{
contractAddress: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5",
fractionalDigits: 5,
ticker: "HASH",
name: "Hash Token",
},
{
contractAddress: "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd",
fractionalDigits: 0,
ticker: "ISA",
name: "Isa Token",
},
{
contractAddress: "cosmos18r5szma8hm93pvx6lwpjwyxruw27e0k5uw835c",
fractionalDigits: 18,
ticker: "JADE",
name: "Jade Token",
},
],
};
const atomConfig: TokenConfiguration = {
@ -198,21 +177,6 @@ describe("CosmWasmConnection", () => {
tokenName: "Fee Token",
tokenTicker: "COSM" as TokenTicker,
},
{
fractionalDigits: 5,
tokenName: "Hash Token",
tokenTicker: "HASH" as TokenTicker,
},
{
fractionalDigits: 0,
tokenName: "Isa Token",
tokenTicker: "ISA" as TokenTicker,
},
{
fractionalDigits: 18,
tokenName: "Jade Token",
tokenTicker: "JADE" as TokenTicker,
},
{
fractionalDigits: 6,
tokenName: "Staking Token",
@ -255,16 +219,6 @@ describe("CosmWasmConnection", () => {
quantity: "1000000000",
fractionalDigits: 6,
},
{
tokenTicker: "HASH" as TokenTicker,
quantity: "12812345",
fractionalDigits: 5,
},
{
tokenTicker: "ISA" as TokenTicker,
quantity: "42",
fractionalDigits: 0,
},
{
tokenTicker: "STAKE" as TokenTicker,
quantity: "1000000000",
@ -408,56 +362,6 @@ describe("CosmWasmConnection", () => {
connection.disconnect();
});
it("can get a recently posted ERC20 send transaction", async () => {
pendingWithoutWasmd();
const connection = await CosmWasmConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
const profile = new UserProfile();
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(bob.mnemonic));
const senderIdentity = await profile.createIdentity(wallet.id, defaultChainId, bob.path);
const senderAddress = connection.codec.identityToAddress(senderIdentity);
const unsigned = await connection.withDefaultFee<SendTransaction>({
kind: "bcp/send",
chainId: defaultChainId,
sender: senderAddress,
recipient: defaultRecipient,
memo: "An ERC20 payment",
amount: {
quantity: "345",
fractionalDigits: 0,
tokenTicker: isa,
},
});
const nonce = await connection.getNonce({ address: senderAddress });
const signed = await profile.signTransaction(senderIdentity, unsigned, connection.codec, nonce);
const postableBytes = connection.codec.bytesToPost(signed);
const response = await connection.postTx(postableBytes);
const { transactionId } = response;
await response.blockInfo.waitFor((info) => isBlockInfoSucceeded(info));
const getResponse = await connection.getTx(transactionId);
expect(getResponse.transactionId).toEqual(transactionId);
assert(isConfirmedTransaction(getResponse), "Expected transaction to succeed");
assert(getResponse.log, "Log must be available");
const [firstLog] = JSON.parse(getResponse.log);
expect(firstLog.events.length).toEqual(2);
const { transaction, signatures } = getResponse;
assert(isSendTransaction(transaction), "Expected send transaction");
expect(transaction).toEqual(unsigned);
expect(signatures.length).toEqual(1);
expect(signatures[0]).toEqual({
nonce: signed.signatures[0].nonce,
pubkey: {
algo: signed.signatures[0].pubkey.algo,
data: Secp256k1.compressPubkey(signed.signatures[0].pubkey.data),
},
signature: Secp256k1.trimRecoveryByte(signed.signatures[0].signature),
});
connection.disconnect();
});
it("can get an old transaction", async () => {
pendingWithoutWasmd();
const connection = await CosmWasmConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
@ -604,124 +508,6 @@ describe("CosmWasmConnection", () => {
connection.disconnect();
});
it("can post an ERC20 transfer and search for the transaction", async () => {
pendingWithoutWasmd();
const connection = await CosmWasmConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
const profile = new UserProfile();
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(bob.mnemonic));
const sender = await profile.createIdentity(wallet.id, defaultChainId, bob.path);
const senderAddress = connection.codec.identityToAddress(sender);
const recipient = makeRandomAddress();
const unsigned = await connection.withDefaultFee<SendTransaction>({
kind: "bcp/send",
chainId: defaultChainId,
sender: senderAddress,
recipient: recipient,
memo: "My first payment",
amount: {
quantity: "75",
fractionalDigits: 0,
tokenTicker: "ISA" as TokenTicker,
},
});
const nonce = await connection.getNonce({ address: senderAddress });
const signed = await profile.signTransaction(sender, unsigned, connection.codec, nonce);
const postableBytes = connection.codec.bytesToPost(signed);
const response = await connection.postTx(postableBytes);
const { transactionId } = response;
const blockInfo = await response.blockInfo.waitFor((info) => !isBlockInfoPending(info));
expect(blockInfo.state).toEqual(TransactionState.Succeeded);
// search by id
const byIdResults = await connection.searchTx({ id: transactionId });
expect(byIdResults.length).toEqual(1);
const byIdResult = byIdResults[0];
expect(byIdResult.transactionId).toEqual(transactionId);
assert(isConfirmedTransaction(byIdResult), "Expected transaction to succeed");
assert(byIdResult.log, "Log must be available");
const [firstByIdlog] = JSON.parse(byIdResult.log);
expect(firstByIdlog.events.length).toEqual(2);
expect(firstByIdlog.events[0].type).toEqual("message");
expect(firstByIdlog.events[1].type).toEqual("wasm");
// wasm event attributes added by contract
expect(firstByIdlog.events[1].attributes).toContain({ key: "action", value: "transfer" });
expect(firstByIdlog.events[1].attributes).toContain({ key: "sender", value: senderAddress });
expect(firstByIdlog.events[1].attributes).toContain({ key: "recipient", value: recipient });
// wasm event attributes added wasmd
expect(firstByIdlog.events[1].attributes).toContain({
key: "contract_address",
value: defaultConfig.erc20Tokens![1].contractAddress,
});
const byIdTransaction = byIdResult.transaction;
assert(isSendTransaction(byIdTransaction), "Expected send transaction");
expect(byIdTransaction).toEqual(unsigned);
// search by sender address
const bySenderResults = await connection.searchTx({ sentFromOrTo: senderAddress });
expect(bySenderResults).toBeTruthy();
expect(bySenderResults.length).toBeGreaterThanOrEqual(1);
const bySenderResult = bySenderResults[bySenderResults.length - 1];
expect(bySenderResult.transactionId).toEqual(transactionId);
assert(isConfirmedTransaction(bySenderResult), "Expected transaction to succeed");
assert(bySenderResult.log, "Log must be available");
const [firstBySenderLog] = JSON.parse(bySenderResult.log);
expect(firstBySenderLog.events.length).toEqual(2);
expect(firstBySenderLog.events[0].type).toEqual("message");
expect(firstBySenderLog.events[1].type).toEqual("wasm");
// wasm event attributes added by contract
expect(firstBySenderLog.events[1].attributes).toContain({ key: "action", value: "transfer" });
expect(firstBySenderLog.events[1].attributes).toContain({ key: "sender", value: senderAddress });
expect(firstBySenderLog.events[1].attributes).toContain({ key: "recipient", value: recipient });
// wasm event attributes added wasmd
expect(firstBySenderLog.events[1].attributes).toContain({
key: "contract_address",
value: defaultConfig.erc20Tokens![1].contractAddress,
});
const bySenderTransaction = bySenderResult.transaction;
assert(isSendTransaction(bySenderTransaction), "Expected send transaction");
expect(bySenderTransaction).toEqual(unsigned);
// search by recipient address
const byRecipientResults = await connection.searchTx({ sentFromOrTo: recipient });
expect(byRecipientResults.length).toBeGreaterThanOrEqual(1);
const byRecipientResult = byRecipientResults[byRecipientResults.length - 1];
expect(byRecipientResult.transactionId).toEqual(transactionId);
assert(isConfirmedTransaction(byRecipientResult), "Expected transaction to succeed");
assert(byRecipientResult.log, "Log must be available");
const [firstByRecipientLog] = JSON.parse(bySenderResult.log);
expect(firstByRecipientLog.events.length).toEqual(2);
expect(firstByRecipientLog.events[0].type).toEqual("message");
expect(firstByRecipientLog.events[1].type).toEqual("wasm");
// wasm event attributes added by contract
expect(firstByRecipientLog.events[1].attributes).toContain({ key: "action", value: "transfer" });
expect(firstByRecipientLog.events[1].attributes).toContain({ key: "sender", value: senderAddress });
expect(firstByRecipientLog.events[1].attributes).toContain({ key: "recipient", value: recipient });
// wasm event attributes added wasmd
expect(firstByRecipientLog.events[1].attributes).toContain({
key: "contract_address",
value: defaultConfig.erc20Tokens![1].contractAddress,
});
const byRecipeintTransaction = byRecipientResult.transaction;
assert(isSendTransaction(byRecipeintTransaction), "Expected send transaction");
expect(byRecipeintTransaction).toEqual(unsigned);
// search by height
const heightResults = await connection.searchTx({ height: byIdResult.height });
expect(heightResults.length).toEqual(1);
const heightResult = heightResults[0];
expect(heightResult.transactionId).toEqual(transactionId);
assert(isConfirmedTransaction(heightResult), "Expected transaction to succeed");
assert(heightResult.log, "Log must be available");
const [firstHeightLog] = JSON.parse(heightResult.log);
expect(firstHeightLog.events.length).toEqual(2);
const heightTransaction = heightResult.transaction;
assert(isSendTransaction(heightTransaction), "Expected send transaction");
expect(heightTransaction).toEqual(unsigned);
connection.disconnect();
});
it("can search by minHeight and maxHeight", async () => {
pendingWithoutWasmd();
const connection = await CosmWasmConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);

View File

@ -1,15 +1,15 @@
import {
CosmWasmClient,
isMsgExecuteContract,
isMsgInstantiateContract,
isMsgStoreCode,
} from "@cosmwasm/cosmwasm";
import { findSequenceForSignedTx, IndexedTx, isMsgSend, isStdTx, SearchTxFilter } from "@cosmwasm/sdk38";
CosmosClient,
findSequenceForSignedTx,
IndexedTx,
isMsgSend,
isStdTx,
SearchTxFilter,
} from "@cosmwasm/sdk38";
import {
Account,
AccountQuery,
AddressQuery,
Amount,
BlockchainConnection,
BlockHeader,
BlockId,
@ -37,7 +37,6 @@ import {
} from "@iov/bcp";
import { Encoding, Uint53 } from "@iov/encoding";
import { concat, DefaultValueProducer, ValueAndUpdates } from "@iov/stream";
import BN from "bn.js";
import equal from "fast-deep-equal";
import { ReadonlyDate } from "readonly-date";
import { Producer, Stream } from "xstream";
@ -47,7 +46,7 @@ import { Caip5 } from "./caip5";
import { CosmWasmCodec } from "./cosmwasmcodec";
import { decodeAmount, decodePubkey, parseTxsResponseSigned, parseTxsResponseUnsigned } from "./decode";
import { buildSignedTx } from "./encode";
import { accountToNonce, BankToken, Erc20Token } from "./types";
import { accountToNonce, BankToken } from "./types";
// poll every 0.5 seconds (block time 1s)
const defaultPollInterval = 500;
@ -55,8 +54,6 @@ const defaultPollInterval = 500;
export interface TokenConfiguration {
/** Supported tokens of the Cosmos SDK bank module */
readonly bankTokens: ReadonlyArray<BankToken & { readonly name: string }>;
/** Smart contract based tokens (ERC20 compatible). Unset means empty array. */
readonly erc20Tokens?: ReadonlyArray<Erc20Token & { readonly name: string }>;
}
function isDefined<X>(value: X | undefined): value is X {
@ -92,43 +89,40 @@ export class CosmWasmConnection implements BlockchainConnection {
addressPrefix: string,
tokens: TokenConfiguration,
): Promise<CosmWasmConnection> {
const cosmWasmClient = new CosmWasmClient(url);
const chainData = await this.initialize(cosmWasmClient);
return new CosmWasmConnection(cosmWasmClient, chainData, addressPrefix, tokens);
const cosmosClient = new CosmosClient(url);
const chainData = await this.initialize(cosmosClient);
return new CosmWasmConnection(cosmosClient, chainData, addressPrefix, tokens);
}
private static async initialize(cosmWasmClient: CosmWasmClient): Promise<ChainId> {
const rawChainId = await cosmWasmClient.getChainId();
private static async initialize(cosmosClient: CosmosClient): Promise<ChainId> {
const rawChainId = await cosmosClient.getChainId();
return Caip5.encode(rawChainId);
}
public readonly chainId: ChainId;
public readonly codec: TxCodec;
private readonly cosmWasmClient: CosmWasmClient;
private readonly cosmosClient: CosmosClient;
private readonly addressPrefix: string;
private readonly bankTokens: readonly BankToken[];
private readonly erc20Tokens: readonly Erc20Token[];
// these are derived from arguments (cached for use in multiple functions)
private readonly feeToken: BankToken | undefined;
private readonly supportedTokens: readonly Token[];
private constructor(
cosmWasmClient: CosmWasmClient,
cosmosClient: CosmosClient,
chainId: ChainId,
addressPrefix: string,
tokens: TokenConfiguration,
) {
this.cosmWasmClient = cosmWasmClient;
this.cosmosClient = cosmosClient;
this.chainId = chainId;
this.codec = new CosmWasmCodec(addressPrefix, tokens.bankTokens, tokens.erc20Tokens);
this.codec = new CosmWasmCodec(addressPrefix, tokens.bankTokens);
this.addressPrefix = addressPrefix;
this.bankTokens = tokens.bankTokens;
this.feeToken = this.bankTokens.find(() => true);
const erc20Tokens = tokens.erc20Tokens || [];
this.erc20Tokens = erc20Tokens;
this.supportedTokens = [...tokens.bankTokens, ...erc20Tokens]
this.supportedTokens = tokens.bankTokens
.map((info) => ({
tokenTicker: info.ticker as TokenTicker,
tokenName: info.name,
@ -142,7 +136,7 @@ export class CosmWasmConnection implements BlockchainConnection {
}
public async height(): Promise<number> {
return this.cosmWasmClient.getHeight();
return this.cosmosClient.getHeight();
}
public async getToken(searchTicker: TokenTicker): Promise<Token | undefined> {
@ -158,40 +152,24 @@ export class CosmWasmConnection implements BlockchainConnection {
* context and network available, which we might use to implement the API in an async way.
*/
public async identifier(signed: SignedTransaction): Promise<TransactionId> {
const tx = buildSignedTx(signed, this.bankTokens, this.erc20Tokens);
const id = await this.cosmWasmClient.getIdentifier(tx);
const tx = buildSignedTx(signed, this.bankTokens);
const id = await this.cosmosClient.getIdentifier(tx);
return id as TransactionId;
}
public async getAccount(query: AccountQuery): Promise<Account | undefined> {
const address = isPubkeyQuery(query) ? pubkeyToAddress(query.pubkey, this.addressPrefix) : query.address;
const bankAccount = await this.cosmWasmClient.getAccount(address);
const bankAccount = await this.cosmosClient.getAccount(address);
const supportedBankCoins = (bankAccount?.balance || []).filter(({ denom }) =>
this.bankTokens.find((token) => token.denom === denom),
);
const erc20Amounts = await Promise.all(
this.erc20Tokens.map(
async (erc20): Promise<Amount> => {
const queryMsg = { balance: { address: address } };
const response = await this.cosmWasmClient.queryContractSmart(erc20.contractAddress, queryMsg);
const normalizedBalance = new BN(response.balance).toString();
return {
fractionalDigits: erc20.fractionalDigits,
quantity: normalizedBalance,
tokenTicker: erc20.ticker as TokenTicker,
};
},
),
);
const nonZeroErc20Amounts = erc20Amounts.filter((amount) => amount.quantity !== "0");
if (!bankAccount && nonZeroErc20Amounts.length === 0) {
if (!bankAccount) {
return undefined;
} else {
const balance = [
...supportedBankCoins.map((coin) => decodeAmount(this.bankTokens, coin)),
...nonZeroErc20Amounts,
].sort((a, b) => a.tokenTicker.localeCompare(b.tokenTicker));
const pubkey = bankAccount?.pubkey ? decodePubkey(bankAccount.pubkey) : undefined;
return {
@ -234,7 +212,7 @@ export class CosmWasmConnection implements BlockchainConnection {
public async getNonce(query: AddressQuery | PubkeyQuery): Promise<Nonce> {
const address = isPubkeyQuery(query) ? pubkeyToAddress(query.pubkey, this.addressPrefix) : query.address;
const { accountNumber, sequence } = await this.cosmWasmClient.getNonce(address);
const { accountNumber, sequence } = await this.cosmosClient.getNonce(address);
return accountToNonce(accountNumber, sequence);
}
@ -249,7 +227,7 @@ export class CosmWasmConnection implements BlockchainConnection {
}
public async getBlockHeader(height: number): Promise<BlockHeader> {
const { id, header, txs } = await this.cosmWasmClient.getBlock(height);
const { id, header, txs } = await this.cosmosClient.getBlock(height);
return {
id: id as BlockId,
height: header.height,
@ -265,7 +243,7 @@ export class CosmWasmConnection implements BlockchainConnection {
public async getTx(
id: TransactionId,
): Promise<ConfirmedAndSignedTransaction<UnsignedTransaction> | FailedTransaction> {
const results = await this.cosmWasmClient.searchTx({ id: id });
const results = await this.cosmosClient.searchTx({ id: id });
switch (results.length) {
case 0:
throw new Error("Transaction does not exist");
@ -279,7 +257,7 @@ export class CosmWasmConnection implements BlockchainConnection {
public async postTx(tx: PostableBytes): Promise<PostTxResponse> {
const txAsJson = JSON.parse(Encoding.fromUtf8(tx));
if (!isStdTx(txAsJson)) throw new Error("Postable bytes must contain a JSON encoded StdTx");
const { transactionHash, rawLog } = await this.cosmWasmClient.postTx(txAsJson);
const { transactionHash, rawLog } = await this.cosmosClient.postTx(txAsJson);
const transactionId = transactionHash as TransactionId;
const firstEvent: BlockInfo = { state: TransactionState.Pending };
let blockInfoInterval: NodeJS.Timeout;
@ -340,36 +318,12 @@ export class CosmWasmConnection implements BlockchainConnection {
let txs: readonly IndexedTx[];
if (id) {
txs = await this.cosmWasmClient.searchTx({ id: id }, filter);
txs = await this.cosmosClient.searchTx({ id: id }, filter);
} else if (height) {
txs = await this.cosmWasmClient.searchTx({ height: height }, filter);
txs = await this.cosmosClient.searchTx({ height: height }, filter);
} else if (sentFromOrTo) {
const pendingRequests = new Array<Promise<readonly IndexedTx[]>>();
pendingRequests.push(this.cosmWasmClient.searchTx({ sentFromOrTo: sentFromOrTo }, filter));
for (const contract of this.erc20Tokens.map((token) => token.contractAddress)) {
const searchBySender = [
{
key: "wasm.contract_address",
value: contract,
},
{
key: "wasm.sender",
value: sentFromOrTo,
},
];
const searchByRecipient = [
{
key: "wasm.contract_address",
value: contract,
},
{
key: "wasm.recipient",
value: sentFromOrTo,
},
];
pendingRequests.push(this.cosmWasmClient.searchTx({ tags: searchBySender }, filter));
pendingRequests.push(this.cosmWasmClient.searchTx({ tags: searchByRecipient }, filter));
}
pendingRequests.push(this.cosmosClient.searchTx({ sentFromOrTo: sentFromOrTo }, filter));
const responses = await Promise.all(pendingRequests);
const allResults = responses.reduce((accumulator, results) => accumulator.concat(results), []);
txs = deduplicate(allResults, (a, b) => a.hash.localeCompare(b.hash)).sort(compareByHeightAndHash);
@ -463,13 +417,7 @@ export class CosmWasmConnection implements BlockchainConnection {
private parseAndPopulateTxResponseUnsigned(
response: IndexedTx,
): ConfirmedTransaction<UnsignedTransaction> | FailedTransaction {
return parseTxsResponseUnsigned(
this.chainId,
response.height,
response,
this.bankTokens,
this.erc20Tokens,
);
return parseTxsResponseUnsigned(this.chainId, response.height, response, this.bankTokens);
}
private async parseAndPopulateTxResponseSigned(
@ -481,17 +429,11 @@ export class CosmWasmConnection implements BlockchainConnection {
let senderAddress: string;
if (isMsgSend(firstMsg)) {
senderAddress = firstMsg.value.from_address;
} else if (
isMsgStoreCode(firstMsg) ||
isMsgInstantiateContract(firstMsg) ||
isMsgExecuteContract(firstMsg)
) {
senderAddress = firstMsg.value.sender;
} else {
throw new Error(`Got unsupported type of message: ${firstMsg.type}`);
}
const { accountNumber, sequence: currentSequence } = await this.cosmWasmClient.getNonce(senderAddress);
const { accountNumber, sequence: currentSequence } = await this.cosmosClient.getNonce(senderAddress);
const sequenceForTx = await findSequenceForSignedTx(
response.tx,
Caip5.decode(this.chainId),
@ -502,14 +444,7 @@ export class CosmWasmConnection implements BlockchainConnection {
const nonce = accountToNonce(accountNumber, sequenceForTx);
return parseTxsResponseSigned(
this.chainId,
response.height,
nonce,
response,
this.bankTokens,
this.erc20Tokens,
);
return parseTxsResponseSigned(this.chainId, response.height, nonce, response, this.bankTokens);
}
private waitForTransaction(

View File

@ -12,7 +12,7 @@ export function createCosmWasmConnector(
tokenConfig: TokenConfiguration,
expectedChainId?: ChainId,
): ChainConnector<CosmWasmConnection> {
const codec = new CosmWasmCodec(addressPrefix, tokenConfig.bankTokens, tokenConfig.erc20Tokens);
const codec = new CosmWasmCodec(addressPrefix, tokenConfig.bankTokens);
return {
establishConnection: async () => CosmWasmConnection.establish(url, addressPrefix, tokenConfig),
codec: codec,

View File

@ -1,9 +1,7 @@
/* eslint-disable @typescript-eslint/camelcase */
import { MsgExecuteContract } from "@cosmwasm/cosmwasm";
import { Coin, IndexedTx, Msg, PubKey, StdSignature, StdTx } from "@cosmwasm/sdk38";
import { Address, Algorithm, isSendTransaction, SendTransaction, TokenTicker } from "@iov/bcp";
import { Coin, IndexedTx, Msg, PubKey, StdSignature } from "@cosmwasm/sdk38";
import { Address, Algorithm, SendTransaction, TokenTicker } from "@iov/bcp";
import { Encoding } from "@iov/encoding";
import { assert } from "@iov/utils";
import {
decodeAmount,
@ -19,7 +17,7 @@ import {
} from "./decode";
import * as testdata from "./testdata.spec";
import cosmoshub from "./testdata/cosmoshub.json";
import { BankToken, Erc20Token } from "./types";
import { BankToken } from "./types";
const { fromBase64, fromHex } = Encoding;
@ -65,23 +63,6 @@ describe("decode", () => {
denom: "uatom",
},
];
const defaultErc20Tokens: Erc20Token[] = [
{
contractAddress: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5",
fractionalDigits: 5,
ticker: "HASH",
},
{
contractAddress: "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd",
fractionalDigits: 0,
ticker: "ISA",
},
{
contractAddress: "cosmos18r5szma8hm93pvx6lwpjwyxruw27e0k5uw835c",
fractionalDigits: 18,
ticker: "JADE",
},
];
describe("decodePubkey", () => {
it("works for secp256k1", () => {
@ -159,40 +140,7 @@ describe("decode", () => {
],
},
};
expect(parseMsg(msg, defaultMemo, testdata.chainId, defaultTokens, defaultErc20Tokens)).toEqual(
defaultSendTransaction,
);
});
it("works for ERC20 send transaction", () => {
const msg: MsgExecuteContract = {
type: "wasm/execute",
value: {
sender: "cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r",
contract: defaultErc20Tokens[0].contractAddress,
msg: {
transfer: {
amount: "887878484",
recipient: "cosmos1z7g5w84ynmjyg0kqpahdjqpj7yq34v3suckp0e",
},
},
sent_funds: [],
},
};
const transaction = parseMsg(msg, defaultMemo, testdata.chainId, defaultTokens, defaultErc20Tokens);
assert(isSendTransaction(transaction));
expect(transaction).toEqual({
kind: "bcp/send",
chainId: testdata.chainId,
sender: "cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r" as Address,
recipient: "cosmos1z7g5w84ynmjyg0kqpahdjqpj7yq34v3suckp0e" as Address,
amount: {
quantity: "887878484",
tokenTicker: "HASH" as TokenTicker,
fractionalDigits: 5,
},
memo: defaultMemo,
});
expect(parseMsg(msg, defaultMemo, testdata.chainId, defaultTokens)).toEqual(defaultSendTransaction);
});
});
@ -213,69 +161,17 @@ describe("decode", () => {
describe("parseUnsignedTx", () => {
it("works for bank send transaction", () => {
expect(
parseUnsignedTx(cosmoshub.tx.value, testdata.chainId, defaultTokens, defaultErc20Tokens),
).toEqual(testdata.sendTxJson);
});
it("works for ERC20 send transaction", () => {
const msg: MsgExecuteContract = {
type: "wasm/execute",
value: {
sender: "cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r",
contract: defaultErc20Tokens[0].contractAddress,
msg: {
transfer: {
amount: "887878484",
recipient: "cosmos1z7g5w84ynmjyg0kqpahdjqpj7yq34v3suckp0e",
},
},
sent_funds: [],
},
};
const tx: StdTx = {
msg: [msg],
memo: defaultMemo,
fee: {
amount: [
{
denom: "uatom",
amount: "5000",
},
],
gas: "200000",
},
signatures: [],
};
const unsigned = parseUnsignedTx(tx, testdata.chainId, defaultTokens, defaultErc20Tokens);
assert(isSendTransaction(unsigned));
expect(unsigned).toEqual({
kind: "bcp/send",
chainId: testdata.chainId,
sender: "cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r" as Address,
recipient: "cosmos1z7g5w84ynmjyg0kqpahdjqpj7yq34v3suckp0e" as Address,
amount: {
quantity: "887878484",
tokenTicker: "HASH" as TokenTicker,
fractionalDigits: 5,
},
memo: defaultMemo,
fee: defaultFee,
});
expect(parseUnsignedTx(cosmoshub.tx.value, testdata.chainId, defaultTokens)).toEqual(
testdata.sendTxJson,
);
});
});
describe("parseSignedTx", () => {
it("works", () => {
expect(
parseSignedTx(
cosmoshub.tx.value,
testdata.chainId,
testdata.nonce,
defaultTokens,
defaultErc20Tokens,
),
).toEqual(testdata.signedTxJson);
expect(parseSignedTx(cosmoshub.tx.value, testdata.chainId, testdata.nonce, defaultTokens)).toEqual(
testdata.signedTxJson,
);
});
});
@ -298,15 +194,9 @@ describe("decode", () => {
transactionId: testdata.txId,
log: '[{"msg_index":0,"success":true,"log":""}]',
};
expect(
parseTxsResponseUnsigned(
testdata.chainId,
currentHeight,
txsResponse,
defaultTokens,
defaultErc20Tokens,
),
).toEqual(expected);
expect(parseTxsResponseUnsigned(testdata.chainId, currentHeight, txsResponse, defaultTokens)).toEqual(
expected,
);
});
});
@ -330,14 +220,7 @@ describe("decode", () => {
log: '[{"msg_index":0,"success":true,"log":""}]',
};
expect(
parseTxsResponseSigned(
testdata.chainId,
currentHeight,
testdata.nonce,
txsResponse,
defaultTokens,
defaultErc20Tokens,
),
parseTxsResponseSigned(testdata.chainId, currentHeight, testdata.nonce, txsResponse, defaultTokens),
).toEqual(expected);
});
});

View File

@ -1,4 +1,3 @@
import { isMsgExecuteContract } from "@cosmwasm/cosmwasm";
import {
Coin,
IndexedTx,
@ -31,9 +30,8 @@ import {
UnsignedTransaction,
} from "@iov/bcp";
import { Decimal, Encoding } from "@iov/encoding";
import BN from "bn.js";
import { BankToken, Erc20Token } from "./types";
import { BankToken } from "./types";
const { fromBase64 } = Encoding;
@ -89,7 +87,6 @@ export function parseMsg(
memo: string | undefined,
chainId: ChainId,
tokens: readonly BankToken[],
erc20Tokens: readonly Erc20Token[],
): UnsignedTransaction {
if (isMsgSend(msg)) {
if (msg.value.amount.length !== 1) {
@ -104,34 +101,6 @@ export function parseMsg(
memo: memo,
};
return send;
} else if (isMsgExecuteContract(msg)) {
const matchingTokenContract = erc20Tokens.find((t) => t.contractAddress === msg.value.contract);
if (!matchingTokenContract) {
return {
chainId: chainId,
kind: "bcp/unknown",
};
}
const recipient: string | undefined = msg.value.msg.transfer?.recipient;
if (!recipient) throw new Error("Could not read recipient");
const amount: string | undefined = msg.value.msg.transfer?.amount;
if (!amount) throw new Error("Could not read recipient");
const send: SendTransaction = {
kind: "bcp/send",
chainId: chainId,
sender: msg.value.sender as Address,
recipient: recipient as Address,
amount: {
quantity: new BN(amount).toString(),
fractionalDigits: matchingTokenContract.fractionalDigits,
tokenTicker: matchingTokenContract.ticker as TokenTicker,
},
memo: memo,
};
return send;
} else {
// Unknown transaction type
const unknown = {
@ -156,7 +125,6 @@ export function parseUnsignedTx(
txValue: StdTx,
chainId: ChainId,
tokens: readonly BankToken[],
erc20Tokens: readonly Erc20Token[],
): UnsignedTransaction {
if (!isStdTx(txValue)) {
throw new Error("Only StdTx is supported");
@ -165,7 +133,7 @@ export function parseUnsignedTx(
throw new Error("Only single-message transactions currently supported");
}
const msg = parseMsg(txValue.msg[0], txValue.memo, chainId, tokens, erc20Tokens);
const msg = parseMsg(txValue.msg[0], txValue.memo, chainId, tokens);
const fee = parseFee(txValue.fee, tokens);
return {
@ -180,11 +148,10 @@ export function parseSignedTx(
chainId: ChainId,
nonce: Nonce,
tokens: readonly BankToken[],
erc20Tokens: readonly Erc20Token[],
): SignedTransaction {
const [primarySignature] = txValue.signatures.map((signature) => decodeFullSignature(signature, nonce));
return {
transaction: parseUnsignedTx(txValue, chainId, tokens, erc20Tokens),
transaction: parseUnsignedTx(txValue, chainId, tokens),
signatures: [primarySignature],
};
}
@ -194,10 +161,9 @@ export function parseTxsResponseUnsigned(
currentHeight: number,
response: IndexedTx,
tokens: readonly BankToken[],
erc20Tokens: readonly Erc20Token[],
): ConfirmedTransaction<UnsignedTransaction> {
return {
transaction: parseUnsignedTx(response.tx.value, chainId, tokens, erc20Tokens),
transaction: parseUnsignedTx(response.tx.value, chainId, tokens),
height: response.height,
confirmations: currentHeight - response.height + 1,
transactionId: response.hash as TransactionId,
@ -211,10 +177,9 @@ export function parseTxsResponseSigned(
nonce: Nonce,
response: IndexedTx,
tokens: readonly BankToken[],
erc20Tokens: readonly Erc20Token[],
): ConfirmedAndSignedTransaction<UnsignedTransaction> {
return {
...parseSignedTx(response.tx.value, chainId, nonce, tokens, erc20Tokens),
...parseSignedTx(response.tx.value, chainId, nonce, tokens),
height: response.height,
confirmations: currentHeight - response.height + 1,
transactionId: response.hash as TransactionId,

View File

@ -20,9 +20,8 @@ import {
encodeFullSignature,
encodePubkey,
toBankCoin,
toErc20Amount,
} from "./encode";
import { BankToken, Erc20Token } from "./types";
import { BankToken } from "./types";
const { fromBase64 } = Encoding;
@ -49,23 +48,6 @@ describe("encode", () => {
denom: "uatom",
},
];
const defaultErc20Tokens: Erc20Token[] = [
{
contractAddress: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5",
fractionalDigits: 5,
ticker: "HASH",
},
{
contractAddress: "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd",
fractionalDigits: 0,
ticker: "ISA",
},
{
contractAddress: "cosmos18r5szma8hm93pvx6lwpjwyxruw27e0k5uw835c",
fractionalDigits: 18,
ticker: "JADE",
},
];
describe("encodePubkey", () => {
it("works for compressed public key", () => {
@ -76,37 +58,6 @@ describe("encode", () => {
});
});
describe("toErc20Amount", () => {
const [ash, bash] = defaultErc20Tokens;
it("encodes an amount", () => {
const amount: Amount = {
quantity: "789",
fractionalDigits: 0,
tokenTicker: "ISA" as TokenTicker,
};
expect(toErc20Amount(amount, bash)).toEqual("789");
});
it("throws on ticker mismatch", () => {
const amount: Amount = {
quantity: "789",
fractionalDigits: 0,
tokenTicker: "ISA" as TokenTicker,
};
expect(() => toErc20Amount(amount, ash)).toThrowError(/ticker mismatch/i);
});
it("throws on ticker mismatch", () => {
const amount: Amount = {
quantity: "789",
fractionalDigits: 2,
tokenTicker: "ISA" as TokenTicker,
};
expect(() => toErc20Amount(amount, bash)).toThrowError(/fractional digits mismatch/i);
});
});
describe("toBankCoin", () => {
it("encodes an amount", () => {
expect(toBankCoin(defaultAmount, defaultTokens)).toEqual({
@ -287,56 +238,6 @@ describe("encode", () => {
},
});
});
it("works for ERC20 send", () => {
const bashSendTx: SendTransaction = {
kind: "bcp/send",
chainId: defaultChainId,
sender: "cosmos1txqfn5jmcts0x0q7krdxj8tgf98tj0965vqlmq" as Address,
recipient: "cosmos1dddd" as Address,
memo: defaultMemo,
amount: {
fractionalDigits: 0,
quantity: "345",
tokenTicker: "ISA" as TokenTicker,
},
fee: {
tokens: {
fractionalDigits: 6,
quantity: "3333",
tokenTicker: "ATOM" as TokenTicker,
},
gasLimit: "234000",
},
};
expect(buildUnsignedTx(bashSendTx, defaultTokens, defaultErc20Tokens)).toEqual({
type: "cosmos-sdk/StdTx",
value: {
msg: [
{
type: "wasm/execute",
value: {
sender: "cosmos1txqfn5jmcts0x0q7krdxj8tgf98tj0965vqlmq",
contract: "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd",
msg: {
transfer: {
recipient: "cosmos1dddd",
amount: "345",
},
},
sent_funds: [],
},
},
],
fee: {
amount: [{ denom: "uatom", amount: "3333" }],
gas: "234000",
},
signatures: [],
memo: defaultMemo,
},
});
});
});
describe("buildSignedTx", () => {

View File

@ -22,7 +22,7 @@ import {
import { Secp256k1 } from "@iov/crypto";
import { Encoding } from "@iov/encoding";
import { BankToken, Erc20Token } from "./types";
import { BankToken } from "./types";
const { toBase64 } = Encoding;
@ -41,14 +41,6 @@ export function encodePubkey(pubkey: PubkeyBundle): PubKey {
}
}
export function toErc20Amount(amount: Amount, erc20Token: Erc20Token): string {
if (amount.tokenTicker !== erc20Token.ticker) throw new Error("Ticker mismatch between amount and token");
if (amount.fractionalDigits !== erc20Token.fractionalDigits) {
throw new Error("Fractional digits mismatch between amount and token");
}
return amount.quantity;
}
export function toBankCoin(amount: Amount, tokens: readonly BankToken[]): Coin {
const match = tokens.find((token) => token.ticker === amount.tokenTicker);
if (!match) throw Error(`unknown ticker: ${amount.tokenTicker}`);
@ -88,17 +80,12 @@ export function encodeFullSignature(fullSignature: FullSignature): StdSignature
}
}
export function buildUnsignedTx(
tx: UnsignedTransaction,
bankTokens: readonly BankToken[],
erc20Tokens: readonly Erc20Token[] = [],
): CosmosSdkTx {
export function buildUnsignedTx(tx: UnsignedTransaction, bankTokens: readonly BankToken[]): CosmosSdkTx {
if (!isSendTransaction(tx)) {
throw new Error("Received transaction of unsupported kind");
}
const matchingBankToken = bankTokens.find((t) => t.ticker === tx.amount.tokenTicker);
const matchingErc20Token = erc20Tokens.find((t) => t.ticker === tx.amount.tokenTicker);
if (!tx.fee) throw new Error("Transaction fee must be set");
@ -121,42 +108,13 @@ export function buildUnsignedTx(
fee: encodeFee(tx.fee, bankTokens),
},
};
} else if (matchingErc20Token) {
return {
type: "cosmos-sdk/StdTx",
value: {
msg: [
{
type: "wasm/execute",
value: {
sender: tx.sender,
contract: matchingErc20Token.contractAddress,
msg: {
transfer: {
amount: toErc20Amount(tx.amount, matchingErc20Token),
recipient: tx.recipient,
},
},
sent_funds: [],
},
},
],
memo: tx.memo || "",
signatures: [],
fee: encodeFee(tx.fee, bankTokens),
},
};
} else {
throw new Error("Cannot encode this type of transaction");
}
}
export function buildSignedTx(
tx: SignedTransaction,
bankTokens: readonly BankToken[],
erc20Tokens: readonly Erc20Token[] = [],
): CosmosSdkTx {
const built = buildUnsignedTx(tx.transaction, bankTokens, erc20Tokens);
export function buildSignedTx(tx: SignedTransaction, bankTokens: readonly BankToken[]): CosmosSdkTx {
const built = buildUnsignedTx(tx.transaction, bankTokens);
return {
...built,
value: {

View File

@ -1,4 +1,4 @@
export { CosmWasmCodec } from "./cosmwasmcodec";
export { CosmWasmConnection, TokenConfiguration } from "./cosmwasmconnection";
export { createCosmWasmConnector } from "./cosmwasmconnector";
export { BankToken, Erc20Token } from "./types";
export { BankToken } from "./types";

View File

@ -15,21 +15,6 @@ export interface BankToken {
readonly fractionalDigits: number;
}
export interface Erc20Token {
readonly contractAddress: string;
readonly ticker: string;
/**
* The number of fractional digits the token supports.
*
* A quantity is expressed as atomic units. 10^fractionalDigits of those
* atomic units make up 1 token.
*
* E.g. in Ethereum 10^18 wei are 1 ETH and from the quantity 123000000000000000000
* the last 18 digits are the fractional part and the rest the wole part.
*/
readonly fractionalDigits: number;
}
// eslint-disable-next-line no-bitwise
const maxAcct = 1 << 23;
// eslint-disable-next-line no-bitwise

View File

@ -10,12 +10,11 @@ import {
TxCodec,
UnsignedTransaction,
} from "@iov/bcp";
import { BankToken, Erc20Token } from "./types";
import { BankToken } from "./types";
export declare class CosmWasmCodec implements TxCodec {
private readonly addressPrefix;
private readonly bankTokens;
private readonly erc20Tokens;
constructor(addressPrefix: string, bankTokens: readonly BankToken[], erc20Tokens?: readonly Erc20Token[]);
constructor(addressPrefix: string, bankTokens: readonly BankToken[]);
bytesToSign(unsigned: UnsignedTransaction, nonce: Nonce): SigningJob;
bytesToPost(signed: SignedTransaction): PostableBytes;
identifier(_signed: SignedTransaction): TransactionId;

View File

@ -22,7 +22,7 @@ import {
UnsignedTransaction,
} from "@iov/bcp";
import { Stream } from "xstream";
import { BankToken, Erc20Token } from "./types";
import { BankToken } from "./types";
export interface TokenConfiguration {
/** Supported tokens of the Cosmos SDK bank module */
readonly bankTokens: ReadonlyArray<
@ -30,12 +30,6 @@ export interface TokenConfiguration {
readonly name: string;
}
>;
/** Smart contract based tokens (ERC20 compatible). Unset means empty array. */
readonly erc20Tokens?: ReadonlyArray<
Erc20Token & {
readonly name: string;
}
>;
}
export declare class CosmWasmConnection implements BlockchainConnection {
static establish(
@ -46,10 +40,9 @@ export declare class CosmWasmConnection implements BlockchainConnection {
private static initialize;
readonly chainId: ChainId;
readonly codec: TxCodec;
private readonly cosmWasmClient;
private readonly cosmosClient;
private readonly addressPrefix;
private readonly bankTokens;
private readonly erc20Tokens;
private readonly feeToken;
private readonly supportedTokens;
private constructor();

View File

@ -13,7 +13,7 @@ import {
UnsignedTransaction,
} from "@iov/bcp";
import { Decimal } from "@iov/encoding";
import { BankToken, Erc20Token } from "./types";
import { BankToken } from "./types";
export declare function decodePubkey(pubkey: PubKey): PubkeyBundle;
export declare function decodeSignature(signature: string): SignatureBytes;
export declare function decodeFullSignature(signature: StdSignature, nonce: number): FullSignature;
@ -24,28 +24,24 @@ export declare function parseMsg(
memo: string | undefined,
chainId: ChainId,
tokens: readonly BankToken[],
erc20Tokens: readonly Erc20Token[],
): UnsignedTransaction;
export declare function parseFee(fee: StdFee, tokens: readonly BankToken[]): Fee;
export declare function parseUnsignedTx(
txValue: StdTx,
chainId: ChainId,
tokens: readonly BankToken[],
erc20Tokens: readonly Erc20Token[],
): UnsignedTransaction;
export declare function parseSignedTx(
txValue: StdTx,
chainId: ChainId,
nonce: Nonce,
tokens: readonly BankToken[],
erc20Tokens: readonly Erc20Token[],
): SignedTransaction;
export declare function parseTxsResponseUnsigned(
chainId: ChainId,
currentHeight: number,
response: IndexedTx,
tokens: readonly BankToken[],
erc20Tokens: readonly Erc20Token[],
): ConfirmedTransaction<UnsignedTransaction>;
export declare function parseTxsResponseSigned(
chainId: ChainId,
@ -53,5 +49,4 @@ export declare function parseTxsResponseSigned(
nonce: Nonce,
response: IndexedTx,
tokens: readonly BankToken[],
erc20Tokens: readonly Erc20Token[],
): ConfirmedAndSignedTransaction<UnsignedTransaction>;

View File

@ -1,18 +1,12 @@
import { Coin, CosmosSdkTx, PubKey, StdFee, StdSignature } from "@cosmwasm/sdk38";
import { Amount, Fee, FullSignature, PubkeyBundle, SignedTransaction, UnsignedTransaction } from "@iov/bcp";
import { BankToken, Erc20Token } from "./types";
import { BankToken } from "./types";
export declare function encodePubkey(pubkey: PubkeyBundle): PubKey;
export declare function toErc20Amount(amount: Amount, erc20Token: Erc20Token): string;
export declare function toBankCoin(amount: Amount, tokens: readonly BankToken[]): Coin;
export declare function encodeFee(fee: Fee, tokens: readonly BankToken[]): StdFee;
export declare function encodeFullSignature(fullSignature: FullSignature): StdSignature;
export declare function buildUnsignedTx(
tx: UnsignedTransaction,
bankTokens: readonly BankToken[],
erc20Tokens?: readonly Erc20Token[],
): CosmosSdkTx;
export declare function buildSignedTx(
tx: SignedTransaction,
bankTokens: readonly BankToken[],
erc20Tokens?: readonly Erc20Token[],
): CosmosSdkTx;
export declare function buildSignedTx(tx: SignedTransaction, bankTokens: readonly BankToken[]): CosmosSdkTx;

View File

@ -1,4 +1,4 @@
export { CosmWasmCodec } from "./cosmwasmcodec";
export { CosmWasmConnection, TokenConfiguration } from "./cosmwasmconnection";
export { createCosmWasmConnector } from "./cosmwasmconnector";
export { BankToken, Erc20Token } from "./types";
export { BankToken } from "./types";

View File

@ -13,20 +13,6 @@ export interface BankToken {
*/
readonly fractionalDigits: number;
}
export interface Erc20Token {
readonly contractAddress: string;
readonly ticker: string;
/**
* The number of fractional digits the token supports.
*
* A quantity is expressed as atomic units. 10^fractionalDigits of those
* atomic units make up 1 token.
*
* E.g. in Ethereum 10^18 wei are 1 ETH and from the quantity 123000000000000000000
* the last 18 digits are the fractional part and the rest the wole part.
*/
readonly fractionalDigits: number;
}
export declare function accountToNonce(accountNumber: number, sequence: number): Nonce;
export declare function nonceToAccountNumber(nonce: Nonce): number;
export declare function nonceToSequence(nonce: Nonce): number;

View File

@ -23,12 +23,4 @@ export const developmentTokenConfig: TokenConfiguration = {
denom: "ustake",
},
],
erc20Tokens: [
{
contractAddress: "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd",
fractionalDigits: 0,
ticker: "ISA",
name: "Isa Token",
},
],
};

View File

@ -30,18 +30,10 @@ const defaultConfig: TokenConfiguration = {
denom: "ustake",
},
],
erc20Tokens: [
{
contractAddress: "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd",
fractionalDigits: 0,
ticker: "ISA",
name: "Isa Token",
},
],
};
const defaultAddressPrefix = "cosmos";
const defaultChainId = "cosmos:testing" as ChainId;
const codec = new CosmWasmCodec(defaultAddressPrefix, defaultConfig.bankTokens, defaultConfig.erc20Tokens);
const codec = new CosmWasmCodec(defaultAddressPrefix, defaultConfig.bankTokens);
function makeRandomAddress(): Address {
return Bech32.encode(defaultAddressPrefix, Random.getBytes(20)) as Address;
@ -100,33 +92,6 @@ describe("Faucet", () => {
]);
connection.disconnect();
});
it("can send ERC20 token", async () => {
pendingWithoutWasmd();
const connection = await CosmWasmConnection.establish(httpUrl, defaultAddressPrefix, defaultConfig);
const { profile, holder } = await makeProfile();
const faucet = new Faucet(defaultConfig, connection, codec, profile);
const recipient = makeRandomAddress();
await faucet.send({
amount: {
quantity: "7",
fractionalDigits: 0,
tokenTicker: "ISA" as TokenTicker,
},
sender: holder,
recipient: recipient,
});
const account = await connection.getAccount({ address: recipient });
assert(account);
expect(account.balance).toEqual([
{
quantity: "7",
fractionalDigits: 0,
tokenTicker: "ISA" as TokenTicker,
},
]);
connection.disconnect();
});
});
describe("refill", () => {
@ -143,18 +108,13 @@ describe("Faucet", () => {
tokenTicker: "COSM",
fractionalDigits: 6,
}),
jasmine.objectContaining({
tokenTicker: "ISA",
fractionalDigits: 0,
}),
jasmine.objectContaining({
tokenTicker: "STAKE",
fractionalDigits: 6,
}),
]);
expect(Number.parseInt(distributorBalance[0].quantity, 10)).toBeGreaterThanOrEqual(80_000000);
expect(Number.parseInt(distributorBalance[1].quantity, 10)).toBeGreaterThanOrEqual(80);
expect(Number.parseInt(distributorBalance[2].quantity, 10)).toBeGreaterThanOrEqual(80_000000);
expect(Number.parseInt(distributorBalance[1].quantity, 10)).toBeGreaterThanOrEqual(80_000000);
connection.disconnect();
});
});
@ -206,7 +166,7 @@ describe("Faucet", () => {
const { profile } = await makeProfile();
const faucet = new Faucet(defaultConfig, connection, codec, profile);
const tickers = await faucet.loadTokenTickers();
expect(tickers).toEqual(["COSM", "ISA", "STAKE"]);
expect(tickers).toEqual(["COSM", "STAKE"]);
connection.disconnect();
});
});

View File

@ -58,9 +58,7 @@ export class TokenManager {
}
private getFractionalDigits(ticker: TokenTicker): number {
const match = [...this.config.bankTokens, ...(this.config.erc20Tokens || [])].find(
(token) => token.ticker === ticker,
);
const match = this.config.bankTokens.find((token) => token.ticker === ticker);
if (!match) throw new Error(`No token found for ticker symbol: ${ticker}`);
return match.fractionalDigits;
}