mirror of
https://github.com/cosmos/cosmjs.git
synced 2025-03-11 14:09:15 +00:00
Merge pull request #71 from confio/erc20-faucet
Add ERC20 send support for faucet
This commit is contained in:
commit
b5e600f5b5
@ -44,6 +44,8 @@
|
||||
"@iov/encoding": "^2.0.0-alpha.7",
|
||||
"@iov/stream": "^2.0.0-alpha.7",
|
||||
"@iov/utils": "^2.0.0-alpha.7",
|
||||
"@types/bn.js": "^4.11.6",
|
||||
"bn.js": "^5.1.1",
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"readonly-date": "^1.0.0",
|
||||
"xstream": "^11.11.0"
|
||||
|
@ -1,64 +1,128 @@
|
||||
import { PostableBytes, PrehashType } from "@iov/bcp";
|
||||
import { CosmosAddressBech32Prefix } from "@cosmwasm/sdk";
|
||||
import { Address, PostableBytes, PrehashType, SendTransaction, TokenTicker } from "@iov/bcp";
|
||||
import { Encoding } from "@iov/encoding";
|
||||
|
||||
import { cosmWasmCodec } from "./cosmwasmcodec";
|
||||
import { CosmWasmCodec } from "./cosmwasmcodec";
|
||||
import { chainId, nonce, sendTxJson, signedTxBin, signedTxEncodedJson, signedTxJson } from "./testdata.spec";
|
||||
import { BankToken, Erc20Token } from "./types";
|
||||
|
||||
const { toUtf8 } = Encoding;
|
||||
|
||||
describe("cosmWasmCodec", () => {
|
||||
const defaultPrefix = "cosmos" as CosmosAddressBech32Prefix;
|
||||
|
||||
const defaultBankTokens: readonly BankToken[] = [
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
ticker: "ATOM",
|
||||
denom: "uatom",
|
||||
},
|
||||
];
|
||||
|
||||
const defaultErc20Tokens: readonly Erc20Token[] = [
|
||||
{
|
||||
contractAddress: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5",
|
||||
fractionalDigits: 5,
|
||||
ticker: "ASH",
|
||||
},
|
||||
{
|
||||
contractAddress: "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd",
|
||||
fractionalDigits: 0,
|
||||
ticker: "BASH",
|
||||
},
|
||||
{
|
||||
contractAddress: "cosmos18r5szma8hm93pvx6lwpjwyxruw27e0k5uw835c",
|
||||
fractionalDigits: 18,
|
||||
ticker: "CASH",
|
||||
},
|
||||
];
|
||||
|
||||
describe("CosmWasmCodec", () => {
|
||||
const codec = new CosmWasmCodec(defaultPrefix, defaultBankTokens, defaultErc20Tokens);
|
||||
|
||||
describe("isValidAddress", () => {
|
||||
it("accepts valid addresses", () => {
|
||||
expect(cosmWasmCodec.isValidAddress("cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6")).toEqual(true);
|
||||
expect(cosmWasmCodec.isValidAddress("cosmosvalcons10q82zkzzmaku5lazhsvxv7hsg4ntpuhdwadmss")).toEqual(
|
||||
true,
|
||||
);
|
||||
expect(cosmWasmCodec.isValidAddress("cosmosvaloper17mggn4znyeyg25wd7498qxl7r2jhgue8u4qjcq")).toEqual(
|
||||
true,
|
||||
);
|
||||
expect(codec.isValidAddress("cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6")).toEqual(true);
|
||||
expect(codec.isValidAddress("cosmosvalcons10q82zkzzmaku5lazhsvxv7hsg4ntpuhdwadmss")).toEqual(true);
|
||||
expect(codec.isValidAddress("cosmosvaloper17mggn4znyeyg25wd7498qxl7r2jhgue8u4qjcq")).toEqual(true);
|
||||
});
|
||||
|
||||
it("rejects invalid addresses", () => {
|
||||
// Bad size
|
||||
expect(cosmWasmCodec.isValidAddress("cosmos10q82zkzzmaku5lazhsvxv7hsg4ntpuhh8289f")).toEqual(false);
|
||||
expect(codec.isValidAddress("cosmos10q82zkzzmaku5lazhsvxv7hsg4ntpuhh8289f")).toEqual(false);
|
||||
// Bad checksum
|
||||
expect(cosmWasmCodec.isValidAddress("cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs7")).toEqual(false);
|
||||
expect(codec.isValidAddress("cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs7")).toEqual(false);
|
||||
// Bad prefix
|
||||
expect(cosmWasmCodec.isValidAddress("cosmot10q82zkzzmaku5lazhsvxv7hsg4ntpuhd8j5266")).toEqual(false);
|
||||
expect(codec.isValidAddress("cosmot10q82zkzzmaku5lazhsvxv7hsg4ntpuhd8j5266")).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
it("properly generates bytes to sign", () => {
|
||||
const expected = {
|
||||
bytes: toUtf8(
|
||||
'{"account_number":"0","chain_id":"cosmoshub-3","fee":{"amount":[{"amount":"2500","denom":"uatom"}],"gas":"100000"},"memo":"","msgs":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"35997500","denom":"uatom"}],"from_address":"cosmos1txqfn5jmcts0x0q7krdxj8tgf98tj0965vqlmq","to_address":"cosmos1nynns8ex9fq6sjjfj8k79ymkdz4sqth06xexae"}}],"sequence":"99"}',
|
||||
),
|
||||
prehashType: PrehashType.Sha256,
|
||||
};
|
||||
const bytesToSign = cosmWasmCodec.bytesToSign(sendTxJson, nonce);
|
||||
describe("bytesToSign", () => {
|
||||
it("works for SendTransaction via bank module", () => {
|
||||
const expected = {
|
||||
bytes: toUtf8(
|
||||
'{"account_number":"0","chain_id":"cosmoshub-3","fee":{"amount":[{"amount":"2500","denom":"uatom"}],"gas":"100000"},"memo":"","msgs":[{"type":"cosmos-sdk/MsgSend","value":{"amount":[{"amount":"35997500","denom":"uatom"}],"from_address":"cosmos1txqfn5jmcts0x0q7krdxj8tgf98tj0965vqlmq","to_address":"cosmos1nynns8ex9fq6sjjfj8k79ymkdz4sqth06xexae"}}],"sequence":"99"}',
|
||||
),
|
||||
prehashType: PrehashType.Sha256,
|
||||
};
|
||||
expect(codec.bytesToSign(sendTxJson, nonce)).toEqual(expected);
|
||||
});
|
||||
|
||||
expect(bytesToSign).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 BASH payment",
|
||||
amount: {
|
||||
fractionalDigits: 0,
|
||||
quantity: "345",
|
||||
tokenTicker: "BASH" 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 BASH 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);
|
||||
});
|
||||
});
|
||||
|
||||
it("properly encodes transactions", () => {
|
||||
const encoded = cosmWasmCodec.bytesToPost(signedTxJson);
|
||||
expect(encoded).toEqual(signedTxEncodedJson);
|
||||
describe("bytesToPost", () => {
|
||||
it("works for SendTransaction via bank module", () => {
|
||||
const encoded = codec.bytesToPost(signedTxJson);
|
||||
expect(encoded).toEqual(signedTxEncodedJson);
|
||||
});
|
||||
});
|
||||
|
||||
it("throws when trying to decode a transaction without a nonce", () => {
|
||||
expect(() => cosmWasmCodec.parseBytes(signedTxBin as PostableBytes, chainId)).toThrowError(
|
||||
/nonce is required/i,
|
||||
);
|
||||
});
|
||||
describe("parseBytes", () => {
|
||||
it("throws when trying to decode a transaction without a nonce", () => {
|
||||
expect(() => codec.parseBytes(signedTxBin as PostableBytes, chainId)).toThrowError(
|
||||
/nonce is required/i,
|
||||
);
|
||||
});
|
||||
|
||||
it("properly decodes transactions", () => {
|
||||
const decoded = cosmWasmCodec.parseBytes(signedTxEncodedJson as PostableBytes, chainId, nonce);
|
||||
expect(decoded).toEqual(signedTxJson);
|
||||
});
|
||||
it("properly decodes transactions", () => {
|
||||
const decoded = codec.parseBytes(signedTxEncodedJson as PostableBytes, chainId, nonce);
|
||||
expect(decoded).toEqual(signedTxJson);
|
||||
});
|
||||
|
||||
it("round trip works", () => {
|
||||
const encoded = cosmWasmCodec.bytesToPost(signedTxJson);
|
||||
const decoded = cosmWasmCodec.parseBytes(encoded, chainId, nonce);
|
||||
expect(decoded).toEqual(signedTxJson);
|
||||
it("round trip works", () => {
|
||||
const encoded = codec.bytesToPost(signedTxJson);
|
||||
const decoded = codec.parseBytes(encoded, chainId, nonce);
|
||||
expect(decoded).toEqual(signedTxJson);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -26,19 +26,25 @@ import { pubkeyToAddress } from "./address";
|
||||
import { Caip5 } from "./caip5";
|
||||
import { parseTx } from "./decode";
|
||||
import { buildSignedTx, buildUnsignedTx } from "./encode";
|
||||
import { BankTokens, nonceToAccountNumber, nonceToSequence } from "./types";
|
||||
import { BankTokens, Erc20Token, nonceToAccountNumber, nonceToSequence } from "./types";
|
||||
|
||||
export class CosmWasmCodec implements TxCodec {
|
||||
private readonly addressPrefix: CosmosAddressBech32Prefix;
|
||||
private readonly tokens: BankTokens;
|
||||
private readonly bankTokens: BankTokens;
|
||||
private readonly erc20Tokens: readonly Erc20Token[];
|
||||
|
||||
public constructor(addressPrefix: CosmosAddressBech32Prefix, tokens: BankTokens) {
|
||||
public constructor(
|
||||
addressPrefix: CosmosAddressBech32Prefix,
|
||||
bankTokens: BankTokens,
|
||||
erc20Tokens: readonly Erc20Token[] = [],
|
||||
) {
|
||||
this.addressPrefix = addressPrefix;
|
||||
this.tokens = tokens;
|
||||
this.bankTokens = bankTokens;
|
||||
this.erc20Tokens = erc20Tokens;
|
||||
}
|
||||
|
||||
public bytesToSign(unsigned: UnsignedTransaction, nonce: Nonce): SigningJob {
|
||||
const built = buildUnsignedTx(unsigned, this.tokens);
|
||||
const built = buildUnsignedTx(unsigned, this.bankTokens, this.erc20Tokens);
|
||||
|
||||
const nonceInfo: types.NonceInfo = {
|
||||
account_number: nonceToAccountNumber(nonce),
|
||||
@ -61,7 +67,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.tokens);
|
||||
const built = buildSignedTx(signed, this.bankTokens, this.erc20Tokens);
|
||||
return marshalTx(built.value) as PostableBytes;
|
||||
}
|
||||
|
||||
@ -79,7 +85,7 @@ export class CosmWasmCodec implements TxCodec {
|
||||
throw new Error("Nonce is required");
|
||||
}
|
||||
const parsed = unmarshalTx(bytes);
|
||||
return parseTx(parsed, chainId, nonce, this.tokens);
|
||||
return parseTx(parsed, chainId, nonce, this.bankTokens);
|
||||
}
|
||||
|
||||
public identityToAddress(identity: Identity): Address {
|
||||
@ -90,16 +96,3 @@ export class CosmWasmCodec implements TxCodec {
|
||||
return isValidAddress(address);
|
||||
}
|
||||
}
|
||||
|
||||
const defaultPrefix = "cosmos" as CosmosAddressBech32Prefix;
|
||||
|
||||
const defaultTokens: BankTokens = [
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
ticker: "ATOM",
|
||||
denom: "uatom",
|
||||
},
|
||||
];
|
||||
|
||||
/** Unconfigured codec is useful for testing only */
|
||||
export const cosmWasmCodec = new CosmWasmCodec(defaultPrefix, defaultTokens);
|
||||
|
@ -11,12 +11,12 @@ import {
|
||||
TokenTicker,
|
||||
TransactionState,
|
||||
} from "@iov/bcp";
|
||||
import { Secp256k1 } from "@iov/crypto";
|
||||
import { Encoding } from "@iov/encoding";
|
||||
import { Random, Secp256k1 } from "@iov/crypto";
|
||||
import { Bech32, Encoding } from "@iov/encoding";
|
||||
import { HdPaths, Secp256k1HdWallet, UserProfile } from "@iov/keycontrol";
|
||||
import { assert } from "@iov/utils";
|
||||
|
||||
import { CosmWasmCodec, cosmWasmCodec } from "./cosmwasmcodec";
|
||||
import { CosmWasmCodec } from "./cosmwasmcodec";
|
||||
import { CosmWasmConnection, TokenConfiguration } from "./cosmwasmconnection";
|
||||
import { signedTxJson, txId } from "./testdata.spec";
|
||||
import { nonceToSequence } from "./types";
|
||||
@ -29,6 +29,12 @@ function pendingWithoutCosmos(): void {
|
||||
}
|
||||
}
|
||||
|
||||
const defaultPrefix = "cosmos" as CosmosAddressBech32Prefix;
|
||||
|
||||
function makeRandomAddress(): Address {
|
||||
return Bech32.encode(defaultPrefix, Random.getBytes(20)) as Address;
|
||||
}
|
||||
|
||||
describe("CosmWasmConnection", () => {
|
||||
const cosm = "COSM" as TokenTicker;
|
||||
const httpUrl = "http://localhost:1317";
|
||||
@ -53,8 +59,6 @@ describe("CosmWasmConnection", () => {
|
||||
address: "cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u" as Address,
|
||||
};
|
||||
|
||||
const defaultPrefix = "cosmos" as CosmosAddressBech32Prefix;
|
||||
|
||||
// this is for wasmd blockchain
|
||||
const defaultConfig: TokenConfiguration = {
|
||||
bankTokens: [
|
||||
@ -84,6 +88,23 @@ describe("CosmWasmConnection", () => {
|
||||
ticker: "BASH",
|
||||
name: "Bash Token",
|
||||
},
|
||||
{
|
||||
contractAddress: "cosmos18r5szma8hm93pvx6lwpjwyxruw27e0k5uw835c",
|
||||
fractionalDigits: 18,
|
||||
ticker: "CASH",
|
||||
name: "Cash Token",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const atomConfig: TokenConfiguration = {
|
||||
bankTokens: [
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
name: "Atom",
|
||||
ticker: "ATOM",
|
||||
denom: "uatom",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@ -154,6 +175,11 @@ describe("CosmWasmConnection", () => {
|
||||
tokenName: "Bash Token",
|
||||
tokenTicker: "BASH" as TokenTicker,
|
||||
},
|
||||
{
|
||||
fractionalDigits: 18,
|
||||
tokenName: "Cash Token",
|
||||
tokenTicker: "CASH" as TokenTicker,
|
||||
},
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
tokenName: "Fee Token",
|
||||
@ -172,8 +198,9 @@ describe("CosmWasmConnection", () => {
|
||||
describe("identifier", () => {
|
||||
it("calculates tx hash from PostableBytes", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);
|
||||
const postable = cosmWasmCodec.bytesToPost(signedTxJson);
|
||||
const codec = new CosmWasmCodec(defaultPrefix, atomConfig.bankTokens);
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, atomConfig);
|
||||
const postable = codec.bytesToPost(signedTxJson);
|
||||
const id = await connection.identifier(postable);
|
||||
expect(id).toMatch(/^[0-9A-F]{64}$/);
|
||||
expect(id).toEqual(txId);
|
||||
@ -242,11 +269,12 @@ describe("CosmWasmConnection", () => {
|
||||
describe("integration tests", () => {
|
||||
it("can post and get a transaction", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const codec = new CosmWasmCodec(defaultPrefix, defaultConfig.bankTokens);
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);
|
||||
const profile = new UserProfile();
|
||||
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(faucetMnemonic));
|
||||
const faucet = await profile.createIdentity(wallet.id, defaultChainId, faucetPath);
|
||||
const faucetAddress = cosmWasmCodec.identityToAddress(faucet);
|
||||
const faucetAddress = codec.identityToAddress(faucet);
|
||||
|
||||
const unsigned = await connection.withDefaultFee<SendTransaction>({
|
||||
kind: "bcp/send",
|
||||
@ -261,8 +289,6 @@ describe("CosmWasmConnection", () => {
|
||||
},
|
||||
});
|
||||
const nonce = await connection.getNonce({ address: faucetAddress });
|
||||
// TODO: we need to use custom codecs everywhere
|
||||
const codec = new CosmWasmCodec(defaultPrefix, defaultConfig.bankTokens);
|
||||
const signed = await profile.signTransaction(faucet, unsigned, codec, nonce);
|
||||
const postableBytes = codec.bytesToPost(signed);
|
||||
const response = await connection.postTx(postableBytes);
|
||||
@ -307,11 +333,12 @@ describe("CosmWasmConnection", () => {
|
||||
|
||||
it("can post and search for a transaction", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const codec = new CosmWasmCodec(defaultPrefix, defaultConfig.bankTokens);
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);
|
||||
const profile = new UserProfile();
|
||||
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(faucetMnemonic));
|
||||
const faucet = await profile.createIdentity(wallet.id, defaultChainId, faucetPath);
|
||||
const faucetAddress = cosmWasmCodec.identityToAddress(faucet);
|
||||
const faucetAddress = codec.identityToAddress(faucet);
|
||||
|
||||
const unsigned = await connection.withDefaultFee<SendTransaction>({
|
||||
kind: "bcp/send",
|
||||
@ -326,8 +353,6 @@ describe("CosmWasmConnection", () => {
|
||||
},
|
||||
});
|
||||
const nonce = await connection.getNonce({ address: faucetAddress });
|
||||
// TODO: we need to use custom codecs everywhere
|
||||
const codec = new CosmWasmCodec(defaultPrefix, defaultConfig.bankTokens);
|
||||
const signed = await profile.signTransaction(faucet, unsigned, codec, nonce);
|
||||
const postableBytes = codec.bytesToPost(signed);
|
||||
const response = await connection.postTx(postableBytes);
|
||||
@ -433,5 +458,47 @@ describe("CosmWasmConnection", () => {
|
||||
|
||||
connection.disconnect();
|
||||
});
|
||||
|
||||
it("can send ERC20 tokens", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const codec = new CosmWasmCodec(defaultPrefix, defaultConfig.bankTokens, defaultConfig.erc20Tokens);
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);
|
||||
const profile = new UserProfile();
|
||||
const wallet = profile.addWallet(Secp256k1HdWallet.fromMnemonic(faucetMnemonic));
|
||||
const faucet = await profile.createIdentity(wallet.id, defaultChainId, faucetPath);
|
||||
const faucetAddress = codec.identityToAddress(faucet);
|
||||
const recipient = makeRandomAddress();
|
||||
|
||||
const unsigned = await connection.withDefaultFee<SendTransaction>({
|
||||
kind: "bcp/send",
|
||||
chainId: defaultChainId,
|
||||
sender: faucetAddress,
|
||||
recipient: recipient,
|
||||
memo: "My first payment",
|
||||
amount: {
|
||||
quantity: "75",
|
||||
fractionalDigits: 0,
|
||||
tokenTicker: "BASH" as TokenTicker,
|
||||
},
|
||||
});
|
||||
const nonce = await connection.getNonce({ address: faucetAddress });
|
||||
const signed = await profile.signTransaction(faucet, unsigned, codec, nonce);
|
||||
const postableBytes = codec.bytesToPost(signed);
|
||||
const response = await connection.postTx(postableBytes);
|
||||
const blockInfo = await response.blockInfo.waitFor(info => !isBlockInfoPending(info));
|
||||
expect(blockInfo.state).toEqual(TransactionState.Succeeded);
|
||||
|
||||
const recipientAccount = await connection.getAccount({ address: recipient });
|
||||
assert(recipientAccount, "Recipient account must have BASH tokens");
|
||||
expect(recipientAccount.balance).toEqual([
|
||||
{
|
||||
tokenTicker: "BASH" as TokenTicker,
|
||||
quantity: "75",
|
||||
fractionalDigits: 0,
|
||||
},
|
||||
]);
|
||||
|
||||
connection.disconnect();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -31,6 +31,7 @@ import {
|
||||
import { Sha256 } from "@iov/crypto";
|
||||
import { Encoding, Uint53 } from "@iov/encoding";
|
||||
import { DefaultValueProducer, ValueAndUpdates } from "@iov/stream";
|
||||
import BN from "bn.js";
|
||||
import equal from "fast-deep-equal";
|
||||
import { ReadonlyDate } from "readonly-date";
|
||||
import { Stream } from "xstream";
|
||||
@ -157,12 +158,10 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
public async getAccount(query: AccountQuery): Promise<Account | undefined> {
|
||||
const address = isPubkeyQuery(query) ? pubkeyToAddress(query.pubkey, this.addressPrefix) : query.address;
|
||||
const { result } = await this.restClient.authAccounts(address);
|
||||
const account = result.value;
|
||||
if (!account.address) {
|
||||
return undefined;
|
||||
}
|
||||
const bankAccount = result.value;
|
||||
const hasBankAccount = !!bankAccount.address;
|
||||
|
||||
const supportedBankCoins = account.coins.filter(({ denom }) =>
|
||||
const supportedBankCoins = bankAccount.coins.filter(({ denom }) =>
|
||||
this.bankTokens.find(token => token.denom === denom),
|
||||
);
|
||||
const erc20Amounts = await Promise.all(
|
||||
@ -172,26 +171,31 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
const response = JSON.parse(
|
||||
await this.restClient.queryContractSmart(erc20.contractAddress, queryMsg),
|
||||
);
|
||||
const normalizedBalance = new BN(response.balance).toString();
|
||||
return {
|
||||
fractionalDigits: erc20.fractionalDigits,
|
||||
quantity: response.balance,
|
||||
quantity: normalizedBalance,
|
||||
tokenTicker: erc20.ticker as TokenTicker,
|
||||
};
|
||||
},
|
||||
),
|
||||
);
|
||||
const nonZeroErc20Amounts = erc20Amounts.filter(amount => amount.quantity !== "0");
|
||||
|
||||
const balance = [
|
||||
...supportedBankCoins.map(coin => decodeAmount(this.bankTokens, coin)),
|
||||
...erc20Amounts,
|
||||
].sort((a, b) => a.tokenTicker.localeCompare(b.tokenTicker));
|
||||
|
||||
const pubkey = !account.public_key ? undefined : decodeCosmosPubkey(account.public_key);
|
||||
return {
|
||||
address: address,
|
||||
balance: balance,
|
||||
pubkey: pubkey,
|
||||
};
|
||||
if (!hasBankAccount && nonZeroErc20Amounts.length === 0) {
|
||||
return undefined;
|
||||
} else {
|
||||
const balance = [
|
||||
...supportedBankCoins.map(coin => decodeAmount(this.bankTokens, coin)),
|
||||
...nonZeroErc20Amounts,
|
||||
].sort((a, b) => a.tokenTicker.localeCompare(b.tokenTicker));
|
||||
const pubkey = !bankAccount.public_key ? undefined : decodeCosmosPubkey(bankAccount.public_key);
|
||||
return {
|
||||
address: address,
|
||||
balance: balance,
|
||||
pubkey: pubkey,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public watchAccount(_account: AccountQuery): Stream<Account | undefined> {
|
||||
@ -291,8 +295,10 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
): Promise<readonly (ConfirmedTransaction<UnsignedTransaction> | FailedTransaction)[]> {
|
||||
const queryString = buildQueryString(query);
|
||||
const chainId = this.chainId();
|
||||
const { txs: responses } = await this.restClient.txs(queryString);
|
||||
return Promise.all(responses.map(response => this.parseAndPopulateTxResponse(response, chainId)));
|
||||
// TODO: we need pagination support
|
||||
const response = await this.restClient.txs(queryString + "&limit=50");
|
||||
const { txs } = response;
|
||||
return Promise.all(txs.map(tx => this.parseAndPopulateTxResponse(tx, chainId)));
|
||||
}
|
||||
|
||||
public listenTx(
|
||||
|
@ -10,12 +10,12 @@ import { CosmWasmConnection, TokenConfiguration } from "./cosmwasmconnection";
|
||||
export function createCosmWasmConnector(
|
||||
url: string,
|
||||
addressPrefix: CosmosAddressBech32Prefix,
|
||||
tokens: TokenConfiguration,
|
||||
tokenConfig: TokenConfiguration,
|
||||
expectedChainId?: ChainId,
|
||||
): ChainConnector<CosmWasmConnection> {
|
||||
const codec = new CosmWasmCodec(addressPrefix, tokens.bankTokens);
|
||||
const codec = new CosmWasmCodec(addressPrefix, tokenConfig.bankTokens, tokenConfig.erc20Tokens);
|
||||
return {
|
||||
establishConnection: async () => CosmWasmConnection.establish(url, addressPrefix, tokens),
|
||||
establishConnection: async () => CosmWasmConnection.establish(url, addressPrefix, tokenConfig),
|
||||
codec: codec,
|
||||
expectedChainId: expectedChainId,
|
||||
};
|
||||
|
@ -16,12 +16,13 @@ import { Encoding } from "@iov/encoding";
|
||||
import {
|
||||
buildSignedTx,
|
||||
buildUnsignedTx,
|
||||
encodeAmount,
|
||||
encodeFee,
|
||||
encodeFullSignature,
|
||||
encodePubkey,
|
||||
toBankCoin,
|
||||
toErc20Amount,
|
||||
} from "./encode";
|
||||
import { BankTokens } from "./types";
|
||||
import { BankTokens, Erc20Token } from "./types";
|
||||
|
||||
const { fromBase64 } = Encoding;
|
||||
|
||||
@ -48,6 +49,23 @@ describe("encode", () => {
|
||||
denom: "uatom",
|
||||
},
|
||||
];
|
||||
const defaultErc20Tokens: Erc20Token[] = [
|
||||
{
|
||||
contractAddress: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5",
|
||||
fractionalDigits: 5,
|
||||
ticker: "ASH",
|
||||
},
|
||||
{
|
||||
contractAddress: "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd",
|
||||
fractionalDigits: 0,
|
||||
ticker: "BASH",
|
||||
},
|
||||
{
|
||||
contractAddress: "cosmos18r5szma8hm93pvx6lwpjwyxruw27e0k5uw835c",
|
||||
fractionalDigits: 18,
|
||||
ticker: "CASH",
|
||||
},
|
||||
];
|
||||
|
||||
describe("encodePubKey", () => {
|
||||
it("encodes a Secp256k1 pubkey", () => {
|
||||
@ -58,9 +76,40 @@ describe("encode", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("encodeAmount", () => {
|
||||
describe("toErc20Amount", () => {
|
||||
const [ash, bash] = defaultErc20Tokens;
|
||||
|
||||
it("encodes an amount", () => {
|
||||
expect(encodeAmount(defaultAmount, defaultTokens)).toEqual({
|
||||
const amount: Amount = {
|
||||
quantity: "789",
|
||||
fractionalDigits: 0,
|
||||
tokenTicker: "BASH" as TokenTicker,
|
||||
};
|
||||
expect(toErc20Amount(amount, bash)).toEqual("789");
|
||||
});
|
||||
|
||||
it("throws on ticker mismatch", () => {
|
||||
const amount: Amount = {
|
||||
quantity: "789",
|
||||
fractionalDigits: 0,
|
||||
tokenTicker: "BASH" as TokenTicker,
|
||||
};
|
||||
expect(() => toErc20Amount(amount, ash)).toThrowError(/ticker mismatch/i);
|
||||
});
|
||||
|
||||
it("throws on ticker mismatch", () => {
|
||||
const amount: Amount = {
|
||||
quantity: "789",
|
||||
fractionalDigits: 2,
|
||||
tokenTicker: "BASH" as TokenTicker,
|
||||
};
|
||||
expect(() => toErc20Amount(amount, bash)).toThrowError(/fractional digits mismatch/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe("toBankCoin", () => {
|
||||
it("encodes an amount", () => {
|
||||
expect(toBankCoin(defaultAmount, defaultTokens)).toEqual({
|
||||
denom: "uatom",
|
||||
amount: "11657995",
|
||||
});
|
||||
@ -262,6 +311,56 @@ 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: "BASH" 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", () => {
|
||||
|
@ -10,9 +10,9 @@ import {
|
||||
SignedTransaction,
|
||||
UnsignedTransaction,
|
||||
} from "@iov/bcp";
|
||||
import { Decimal, Encoding } from "@iov/encoding";
|
||||
import { Encoding } from "@iov/encoding";
|
||||
|
||||
import { BankTokens } from "./types";
|
||||
import { BankTokens, Erc20Token } from "./types";
|
||||
|
||||
const { toBase64 } = Encoding;
|
||||
|
||||
@ -33,30 +33,28 @@ export function encodePubkey(pubkey: PubkeyBundle): types.PubKey {
|
||||
}
|
||||
}
|
||||
|
||||
export function decimalToCoin(lookup: BankTokens, value: Decimal, ticker: string): types.Coin {
|
||||
const match = lookup.find(token => token.ticker === ticker);
|
||||
if (!match) {
|
||||
throw Error(`unknown ticker: ${ticker}`);
|
||||
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");
|
||||
}
|
||||
if (match.fractionalDigits !== value.fractionalDigits) {
|
||||
return amount.quantity;
|
||||
}
|
||||
|
||||
export function toBankCoin(amount: Amount, tokens: BankTokens): types.Coin {
|
||||
const match = tokens.find(token => token.ticker === amount.tokenTicker);
|
||||
if (!match) throw Error(`unknown ticker: ${amount.tokenTicker}`);
|
||||
if (match.fractionalDigits !== amount.fractionalDigits) {
|
||||
throw new Error(
|
||||
"Mismatch in fractional digits between token and value. If you really want, implement a conversion here. However, this indicates a bug in the caller code.",
|
||||
);
|
||||
}
|
||||
return {
|
||||
denom: match.denom,
|
||||
amount: value.atomics,
|
||||
amount: amount.quantity,
|
||||
};
|
||||
}
|
||||
|
||||
export function encodeAmount(amount: Amount, tokens: BankTokens): types.Coin {
|
||||
return decimalToCoin(
|
||||
tokens,
|
||||
Decimal.fromAtomics(amount.quantity, amount.fractionalDigits),
|
||||
amount.tokenTicker,
|
||||
);
|
||||
}
|
||||
|
||||
export function encodeFee(fee: Fee, tokens: BankTokens): types.StdFee {
|
||||
if (fee.tokens === undefined) {
|
||||
throw new Error("Cannot encode fee without tokens");
|
||||
@ -65,7 +63,7 @@ export function encodeFee(fee: Fee, tokens: BankTokens): types.StdFee {
|
||||
throw new Error("Cannot encode fee without gas limit");
|
||||
}
|
||||
return {
|
||||
amount: [encodeAmount(fee.tokens, tokens)],
|
||||
amount: [toBankCoin(fee.tokens, tokens)],
|
||||
gas: fee.gasLimit,
|
||||
};
|
||||
}
|
||||
@ -79,37 +77,83 @@ export function encodeFullSignature(fullSignature: FullSignature): types.StdSign
|
||||
}
|
||||
}
|
||||
|
||||
export function buildUnsignedTx(tx: UnsignedTransaction, tokens: BankTokens): types.AminoTx {
|
||||
export function buildUnsignedTx(
|
||||
tx: UnsignedTransaction,
|
||||
bankTokens: BankTokens,
|
||||
erc20Tokens: readonly Erc20Token[] = [],
|
||||
): types.AminoTx {
|
||||
if (!isSendTransaction(tx)) {
|
||||
throw new Error("Received transaction of unsupported kind");
|
||||
}
|
||||
return {
|
||||
type: "cosmos-sdk/StdTx",
|
||||
value: {
|
||||
msg: [
|
||||
{
|
||||
type: "cosmos-sdk/MsgSend",
|
||||
value: {
|
||||
from_address: tx.sender,
|
||||
to_address: tx.recipient,
|
||||
amount: [encodeAmount(tx.amount, tokens)],
|
||||
|
||||
const matchingBankToken = bankTokens.find(t => t.ticker === tx.amount.tokenTicker);
|
||||
const matchingErc20Token = erc20Tokens.find(t => t.ticker === tx.amount.tokenTicker);
|
||||
|
||||
if (matchingBankToken) {
|
||||
return {
|
||||
type: "cosmos-sdk/StdTx",
|
||||
value: {
|
||||
msg: [
|
||||
{
|
||||
type: "cosmos-sdk/MsgSend",
|
||||
value: {
|
||||
from_address: tx.sender,
|
||||
to_address: tx.recipient,
|
||||
amount: [toBankCoin(tx.amount, bankTokens)],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
memo: tx.memo || "",
|
||||
signatures: [],
|
||||
fee: tx.fee
|
||||
? encodeFee(tx.fee, tokens)
|
||||
: {
|
||||
amount: [],
|
||||
gas: "",
|
||||
],
|
||||
memo: tx.memo || "",
|
||||
signatures: [],
|
||||
fee: tx.fee
|
||||
? encodeFee(tx.fee, bankTokens)
|
||||
: {
|
||||
amount: [],
|
||||
gas: "",
|
||||
},
|
||||
},
|
||||
};
|
||||
} 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: tx.fee
|
||||
? encodeFee(tx.fee, bankTokens)
|
||||
: {
|
||||
amount: [],
|
||||
gas: "",
|
||||
},
|
||||
},
|
||||
};
|
||||
} else {
|
||||
throw new Error("Cannot encode this type of transaction");
|
||||
}
|
||||
}
|
||||
|
||||
export function buildSignedTx(tx: SignedTransaction, tokens: BankTokens): types.AminoTx {
|
||||
const built = buildUnsignedTx(tx.transaction, tokens);
|
||||
export function buildSignedTx(
|
||||
tx: SignedTransaction,
|
||||
bankTokens: BankTokens,
|
||||
erc20Tokens: readonly Erc20Token[] = [],
|
||||
): types.AminoTx {
|
||||
const built = buildUnsignedTx(tx.transaction, bankTokens, erc20Tokens);
|
||||
return {
|
||||
...built,
|
||||
value: {
|
||||
|
13
packages/bcp/types/cosmwasmcodec.d.ts
vendored
13
packages/bcp/types/cosmwasmcodec.d.ts
vendored
@ -11,11 +11,16 @@ import {
|
||||
TxCodec,
|
||||
UnsignedTransaction,
|
||||
} from "@iov/bcp";
|
||||
import { BankTokens } from "./types";
|
||||
import { BankTokens, Erc20Token } from "./types";
|
||||
export declare class CosmWasmCodec implements TxCodec {
|
||||
private readonly addressPrefix;
|
||||
private readonly tokens;
|
||||
constructor(addressPrefix: CosmosAddressBech32Prefix, tokens: BankTokens);
|
||||
private readonly bankTokens;
|
||||
private readonly erc20Tokens;
|
||||
constructor(
|
||||
addressPrefix: CosmosAddressBech32Prefix,
|
||||
bankTokens: BankTokens,
|
||||
erc20Tokens?: readonly Erc20Token[],
|
||||
);
|
||||
bytesToSign(unsigned: UnsignedTransaction, nonce: Nonce): SigningJob;
|
||||
bytesToPost(signed: SignedTransaction): PostableBytes;
|
||||
identifier(_signed: SignedTransaction): TransactionId;
|
||||
@ -23,5 +28,3 @@ export declare class CosmWasmCodec implements TxCodec {
|
||||
identityToAddress(identity: Identity): Address;
|
||||
isValidAddress(address: string): boolean;
|
||||
}
|
||||
/** Unconfigured codec is useful for testing only */
|
||||
export declare const cosmWasmCodec: CosmWasmCodec;
|
||||
|
2
packages/bcp/types/cosmwasmconnector.d.ts
vendored
2
packages/bcp/types/cosmwasmconnector.d.ts
vendored
@ -7,6 +7,6 @@ import { CosmWasmConnection, TokenConfiguration } from "./cosmwasmconnection";
|
||||
export declare function createCosmWasmConnector(
|
||||
url: string,
|
||||
addressPrefix: CosmosAddressBech32Prefix,
|
||||
tokens: TokenConfiguration,
|
||||
tokenConfig: TokenConfiguration,
|
||||
expectedChainId?: ChainId,
|
||||
): ChainConnector<CosmWasmConnection>;
|
||||
|
19
packages/bcp/types/encode.d.ts
vendored
19
packages/bcp/types/encode.d.ts
vendored
@ -1,11 +1,18 @@
|
||||
import { types } from "@cosmwasm/sdk";
|
||||
import { Amount, Fee, FullSignature, PubkeyBundle, SignedTransaction, UnsignedTransaction } from "@iov/bcp";
|
||||
import { Decimal } from "@iov/encoding";
|
||||
import { BankTokens } from "./types";
|
||||
import { BankTokens, Erc20Token } from "./types";
|
||||
export declare function encodePubkey(pubkey: PubkeyBundle): types.PubKey;
|
||||
export declare function decimalToCoin(lookup: BankTokens, value: Decimal, ticker: string): types.Coin;
|
||||
export declare function encodeAmount(amount: Amount, tokens: BankTokens): types.Coin;
|
||||
export declare function toErc20Amount(amount: Amount, erc20Token: Erc20Token): string;
|
||||
export declare function toBankCoin(amount: Amount, tokens: BankTokens): types.Coin;
|
||||
export declare function encodeFee(fee: Fee, tokens: BankTokens): types.StdFee;
|
||||
export declare function encodeFullSignature(fullSignature: FullSignature): types.StdSignature;
|
||||
export declare function buildUnsignedTx(tx: UnsignedTransaction, tokens: BankTokens): types.AminoTx;
|
||||
export declare function buildSignedTx(tx: SignedTransaction, tokens: BankTokens): types.AminoTx;
|
||||
export declare function buildUnsignedTx(
|
||||
tx: UnsignedTransaction,
|
||||
bankTokens: BankTokens,
|
||||
erc20Tokens?: readonly Erc20Token[],
|
||||
): types.AminoTx;
|
||||
export declare function buildSignedTx(
|
||||
tx: SignedTransaction,
|
||||
bankTokens: BankTokens,
|
||||
erc20Tokens?: readonly Erc20Token[],
|
||||
): types.AminoTx;
|
||||
|
@ -116,7 +116,7 @@ situation is different.
|
||||
```
|
||||
curl --header "Content-Type: application/json" \
|
||||
--request POST \
|
||||
--data '{"ticker":"CASH","address":"tiov1k898u78hgs36uqw68dg7va5nfkgstu5z0fhz3f"}' \
|
||||
--data '{"ticker":"BASH","address":"cosmos1yre6ac7qfgyfgvh58ph0rgw627rhw766y430qq"}' \
|
||||
http://localhost:8000/credit
|
||||
```
|
||||
|
||||
|
@ -19,7 +19,7 @@ export async function start(args: ReadonlyArray<string>): Promise<void> {
|
||||
const connector = createCosmWasmConnector(
|
||||
blockchainBaseUrl,
|
||||
constants.addressPrefix,
|
||||
constants.tokenConfig,
|
||||
constants.developmentTokenConfig,
|
||||
);
|
||||
console.info(`Connecting to blockchain ${blockchainBaseUrl} ...`);
|
||||
const connection = await connector.establishConnection();
|
||||
@ -35,7 +35,7 @@ export async function start(args: ReadonlyArray<string>): Promise<void> {
|
||||
);
|
||||
|
||||
// Faucet
|
||||
const faucet = new Faucet(constants.tokenConfig, connection, connector.codec, profile, true);
|
||||
const faucet = new Faucet(constants.developmentTokenConfig, connection, connector.codec, profile, true);
|
||||
const chainTokens = await faucet.loadTokenTickers();
|
||||
console.info("Chain tokens:", chainTokens);
|
||||
const accounts = await faucet.loadAccounts();
|
||||
|
@ -6,7 +6,9 @@ export const port: number = Number.parseInt(process.env.FAUCET_PORT || "", 10) |
|
||||
export const mnemonic: string | undefined = process.env.FAUCET_MNEMONIC;
|
||||
|
||||
export const addressPrefix = "cosmos";
|
||||
export const tokenConfig: TokenConfiguration = {
|
||||
|
||||
/** For the local development chain */
|
||||
export const developmentTokenConfig: TokenConfiguration = {
|
||||
bankTokens: [
|
||||
{
|
||||
fractionalDigits: 6,
|
||||
@ -21,4 +23,12 @@ export const tokenConfig: TokenConfiguration = {
|
||||
denom: "ustake",
|
||||
},
|
||||
],
|
||||
erc20Tokens: [
|
||||
{
|
||||
contractAddress: "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd",
|
||||
fractionalDigits: 0,
|
||||
ticker: "BASH",
|
||||
name: "Bash Token",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -32,23 +32,17 @@ const defaultConfig: TokenConfiguration = {
|
||||
},
|
||||
],
|
||||
erc20Tokens: [
|
||||
// {
|
||||
// contractAddress: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5",
|
||||
// fractionalDigits: 5,
|
||||
// ticker: "ASH",
|
||||
// name: "Ash Token",
|
||||
// },
|
||||
// {
|
||||
// contractAddress: "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd",
|
||||
// fractionalDigits: 0,
|
||||
// ticker: "BASH",
|
||||
// name: "Bash Token",
|
||||
// },
|
||||
{
|
||||
contractAddress: "cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd",
|
||||
fractionalDigits: 0,
|
||||
ticker: "BASH",
|
||||
name: "Bash Token",
|
||||
},
|
||||
],
|
||||
};
|
||||
const defaultPrefix = "cosmos" as CosmosAddressBech32Prefix;
|
||||
const defaultChainId = "cosmos:testing" as ChainId;
|
||||
const codec = new CosmWasmCodec(defaultPrefix, defaultConfig.bankTokens);
|
||||
const codec = new CosmWasmCodec(defaultPrefix, defaultConfig.bankTokens, defaultConfig.erc20Tokens);
|
||||
|
||||
function makeRandomAddress(): Address {
|
||||
return Bech32.encode(defaultPrefix, Random.getBytes(20)) as Address;
|
||||
@ -81,7 +75,7 @@ describe("Faucet", () => {
|
||||
});
|
||||
|
||||
describe("send", () => {
|
||||
it("can send", async () => {
|
||||
it("can send bank token", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);
|
||||
const { profile, holder } = await makeProfile();
|
||||
@ -107,6 +101,33 @@ describe("Faucet", () => {
|
||||
]);
|
||||
connection.disconnect();
|
||||
});
|
||||
|
||||
it("can send ERC20 token", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, 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: "BASH" as TokenTicker,
|
||||
},
|
||||
sender: holder,
|
||||
recipient: recipient,
|
||||
});
|
||||
const account = await connection.getAccount({ address: recipient });
|
||||
assert(account);
|
||||
expect(account.balance).toEqual([
|
||||
{
|
||||
quantity: "7",
|
||||
fractionalDigits: 0,
|
||||
tokenTicker: "BASH" as TokenTicker,
|
||||
},
|
||||
]);
|
||||
connection.disconnect();
|
||||
});
|
||||
});
|
||||
|
||||
describe("refill", () => {
|
||||
@ -119,6 +140,10 @@ describe("Faucet", () => {
|
||||
const distributorBalance = (await connection.getAccount({ pubkey: distributors[0].pubkey }))?.balance;
|
||||
assert(distributorBalance);
|
||||
expect(distributorBalance).toEqual([
|
||||
jasmine.objectContaining({
|
||||
tokenTicker: "BASH",
|
||||
fractionalDigits: 0,
|
||||
}),
|
||||
jasmine.objectContaining({
|
||||
tokenTicker: "COSM",
|
||||
fractionalDigits: 6,
|
||||
@ -128,8 +153,9 @@ describe("Faucet", () => {
|
||||
fractionalDigits: 6,
|
||||
}),
|
||||
]);
|
||||
expect(Number.parseInt(distributorBalance[0].quantity, 10)).toBeGreaterThanOrEqual(80_000000);
|
||||
expect(Number.parseInt(distributorBalance[0].quantity, 10)).toBeGreaterThanOrEqual(80);
|
||||
expect(Number.parseInt(distributorBalance[1].quantity, 10)).toBeGreaterThanOrEqual(80_000000);
|
||||
expect(Number.parseInt(distributorBalance[2].quantity, 10)).toBeGreaterThanOrEqual(80_000000);
|
||||
connection.disconnect();
|
||||
});
|
||||
});
|
||||
@ -181,7 +207,7 @@ describe("Faucet", () => {
|
||||
const { profile } = await makeProfile();
|
||||
const faucet = new Faucet(defaultConfig, connection, codec, profile);
|
||||
const tickers = await faucet.loadTokenTickers();
|
||||
expect(tickers).toEqual(["COSM", "STAKE"]);
|
||||
expect(tickers).toEqual(["BASH", "COSM", "STAKE"]);
|
||||
connection.disconnect();
|
||||
});
|
||||
});
|
||||
|
@ -146,7 +146,7 @@ export class Faucet {
|
||||
}
|
||||
if (jobs.length > 0) {
|
||||
for (const job of jobs) {
|
||||
logSendJob(job);
|
||||
if (this.logging) logSendJob(job);
|
||||
await this.send(job);
|
||||
await sleep(50);
|
||||
}
|
||||
|
@ -118,7 +118,18 @@ async function main() {
|
||||
},
|
||||
],
|
||||
};
|
||||
for (const initMsg of [initMsgAsh, initMsgBash]) {
|
||||
const initMsgCash = {
|
||||
decimals: 18,
|
||||
name: "Cash Token",
|
||||
symbol: "CASH",
|
||||
initial_balances: [
|
||||
{
|
||||
address: faucetAddress,
|
||||
amount: "189189189000000000000000000", // 189189189 CASH
|
||||
},
|
||||
],
|
||||
};
|
||||
for (const initMsg of [initMsgAsh, initMsgBash, initMsgCash]) {
|
||||
const initResult = await instantiateContract(client, pen, codeId, initMsg);
|
||||
if (initResult.code) {
|
||||
throw new Error(`Instantiation failed with code: ${initResult.code}; log: '${initResult.raw_log}'`);
|
||||
|
12
yarn.lock
12
yarn.lock
@ -966,6 +966,13 @@
|
||||
dependencies:
|
||||
"@types/babel-types" "*"
|
||||
|
||||
"@types/bn.js@^4.11.6":
|
||||
version "4.11.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c"
|
||||
integrity sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/body-parser@*":
|
||||
version "1.17.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.17.1.tgz#18fcf61768fb5c30ccc508c21d6fd2e8b3bf7897"
|
||||
@ -1826,6 +1833,11 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.11.8, bn.js@^4.4.0:
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
|
||||
integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==
|
||||
|
||||
bn.js@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.1.tgz#48efc4031a9c4041b9c99c6941d903463ab62eb5"
|
||||
integrity sha512-IUTD/REb78Z2eodka1QZyyEk66pciRcP6Sroka0aI3tG/iwIdYLrBD62RsubR7vqdt3WyX8p4jxeatzmRSphtA==
|
||||
|
||||
body-parser@^1.16.1:
|
||||
version "1.19.0"
|
||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
|
||||
|
Loading…
x
Reference in New Issue
Block a user