Skip to content

Commit ea3ffa5

Browse files
committed
add fee and export
1 parent 820edb8 commit ea3ffa5

File tree

5 files changed

+125
-18
lines changed

5 files changed

+125
-18
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import Decimal from 'decimal.js';
2+
import { GasPrice, calculateFee } from './fee';
3+
4+
describe('GasPrice', () => {
5+
test('fromString parses valid gas price', () => {
6+
const gp = GasPrice.fromString('0.025uatom');
7+
expect(gp.denom).toBe('uatom');
8+
expect(gp.amount.equals(new Decimal('0.025'))).toBe(true);
9+
});
10+
11+
test('toString roundtrips', () => {
12+
const gp = GasPrice.fromString('0.0125uosmo');
13+
expect(gp.toString()).toBe('0.0125uosmo');
14+
});
15+
16+
test('invalid string throws', () => {
17+
expect(() => GasPrice.fromString('abc')).toThrow('Invalid gas price string');
18+
expect(() => GasPrice.fromString('0.1')).toThrow('Invalid gas price string');
19+
expect(() => GasPrice.fromString('0.1_foo')).toThrow('Invalid gas price string');
20+
});
21+
});
22+
23+
describe('calculateFee', () => {
24+
test('works with number gasLimit', () => {
25+
const fee = calculateFee(200000, '0.025uatom');
26+
expect(fee.gas).toBe('200000');
27+
expect(fee.amount).toEqual([{ denom: 'uatom', amount: '5000' }]);
28+
});
29+
30+
test('works with bigint gasLimit', () => {
31+
const fee = calculateFee(3n, '0.4ufoo');
32+
expect(fee.gas).toBe('3');
33+
expect(fee.amount).toEqual([{ denom: 'ufoo', amount: '2' }]); // ceil(1.2) = 2
34+
});
35+
36+
test('rounds up small fractional result to 1', () => {
37+
const fee = calculateFee(3, '0.0001utest');
38+
expect(fee.amount).toEqual([{ denom: 'utest', amount: '1' }]);
39+
});
40+
});
41+

networks/cosmos/src/utils/fee.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import Decimal from 'decimal.js';
2+
import { StdFee } from '@interchainjs/types';
3+
4+
/**
5+
* Minimal denom checker similar to CosmJS, permissive with basic safety.
6+
*/
7+
function checkDenom(denom: string): void {
8+
if (!denom) throw new Error('denom must not be empty');
9+
if (denom.length < 3 || denom.length > 128) {
10+
throw new Error('Denom must be between 3 and 128 characters');
11+
}
12+
}
13+
14+
/**
15+
* A gas price, i.e. the price of a single unit of gas. Typically a fraction of the smallest fee token unit.
16+
*/
17+
export class GasPrice {
18+
public readonly amount: Decimal;
19+
public readonly denom: string;
20+
21+
constructor(amount: Decimal, denom: string) {
22+
checkDenom(denom);
23+
this.amount = amount;
24+
this.denom = denom;
25+
}
26+
27+
/**
28+
* Parses a gas price formatted as `<amount><denom>`, e.g. "0.012uatom".
29+
* Separators are not supported.
30+
*/
31+
public static fromString(gasPrice: string): GasPrice {
32+
const match = gasPrice.match(/^([0-9]+(?:\.[0-9]+)?)([a-zA-Z][a-zA-Z0-9/:._-]*)$/);
33+
if (!match) {
34+
throw new Error('Invalid gas price string');
35+
}
36+
const [, amountStr, denom] = match;
37+
checkDenom(denom);
38+
const amount = new Decimal(amountStr);
39+
return new GasPrice(amount, denom);
40+
}
41+
42+
/** Returns a string representation such as "0.025uatom". */
43+
public toString(): string {
44+
return this.amount.toString() + this.denom;
45+
}
46+
}
47+
48+
/**
49+
* Calculate StdFee from a gas limit and gas price. Rounds amount up to the nearest integer unit.
50+
*/
51+
export function calculateFee(gasLimit: number | bigint, gasPrice: GasPrice | string): StdFee {
52+
const processed = typeof gasPrice === 'string' ? GasPrice.fromString(gasPrice) : gasPrice;
53+
const gasLimitDec = new Decimal(typeof gasLimit === 'bigint' ? gasLimit.toString() : gasLimit);
54+
const amount = processed.amount.mul(gasLimitDec).ceil();
55+
return {
56+
amount: [{ denom: processed.denom, amount: amount.toFixed(0) }],
57+
gas: (typeof gasLimit === 'bigint' ? gasLimit.toString() : String(gasLimit)),
58+
};
59+
}
60+

networks/cosmos/src/utils.ts renamed to networks/cosmos/src/utils/index.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { generateMnemonic } from '@interchainjs/crypto';
2-
import { AminoConverter, Encoder } from './types/signing-client';
2+
import { AminoConverter, Encoder } from '../types/signing-client';
33
import { TelescopeGeneratedCodec } from '@interchainjs/types';
44
import { assertEmpty } from '@interchainjs/utils';
55
import { Pubkey } from "@interchainjs/amino";
@@ -228,4 +228,11 @@ export function toEncoders(
228228
...generatedArray: (Encoder | TelescopeGeneratedCodec<any, any, any>)[]
229229
): Encoder[] {
230230
return generatedArray.map((generated) => toEncoder(generated));
231-
}
231+
}
232+
233+
// Re-export selected utilities used by starship tests, if needed
234+
export { generateMnemonic };
235+
236+
// Re-export fee helpers
237+
export * from './fee';
238+

networks/cosmos/src/utils.test.ts renamed to networks/cosmos/src/utils/utils.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { accountFromAny, Account } from "./utils";
1+
import { accountFromAny, Account } from ".";
22
import {
33
BaseAccount,
44
ModuleAccount,
@@ -295,3 +295,4 @@ describe("accountFromAny", () => {
295295
});
296296
});
297297
});
298+

networks/cosmos/src/workflows/plugins/fee-calculation.ts

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { INPUT_VALIDATION_STAGING_KEYS } from './input-validation';
66
import { MESSAGE_ENCODING_STAGING_KEYS } from './message-encoding';
77
import { SIGNER_INFO_STAGING_KEYS } from './signer-info';
88
import { Price } from '@interchainjs/types';
9-
import Decimal from 'decimal.js';
9+
import { calculateFee } from '../../utils/fee';
1010

1111
/**
1212
* Staging keys created by FeeCalculationPlugin
@@ -64,25 +64,23 @@ export class FeeCalculationPlugin extends BaseWorkflowBuilderPlugin<
6464
const multiplier = params.options?.multiplier ?? 1.5;
6565
const gasLimit = BigInt(Math.ceil(Number(gasUsed) * multiplier));
6666

67-
// Default gas price ( Price | 'average' | 'high' | 'low' | undefined)
68-
let denom = "uatom";
69-
let gasPriceValue = new Decimal('0.025');
67+
// Resolve gas price to a concrete string value
68+
let gasPriceString = "0.025uatom"; // Default fallback
7069

71-
// ex) 123.123uatom -> gasPriceValue: "123.123", denom: "uatom")
7270
if (typeof params.options?.gasPrice === 'string') {
73-
const numberMatch = params.options.gasPrice.match(/^(\d+(?:\.\d+)?)(.*)$/);
74-
if (numberMatch && numberMatch.length === 3) {
75-
gasPriceValue = new Decimal(numberMatch[1]);
76-
denom = numberMatch[2];
71+
// Handle concrete gas price strings like "0.025uatom" or abstract values like "average"
72+
if (params.options.gasPrice.match(/^(\d+(?:\.\d+)?)(.*)$/)) {
73+
// It's a concrete gas price string, use it directly
74+
gasPriceString = params.options.gasPrice;
75+
} else {
76+
// It's an abstract value like "average", "high", "low" - keep default for now
77+
// TODO: In the future, this could be resolved from chain registry or network config
78+
gasPriceString = "0.025uatom";
7779
}
7880
}
7981

80-
const amount = gasPriceValue.mul(gasLimit.toString()).ceil();
81-
82-
finalFee = {
83-
amount: [{ denom, amount: amount.toString() }],
84-
gas: gasLimit.toString(),
85-
};
82+
// Use the new calculateFee utility for consistent fee calculation
83+
finalFee = calculateFee(gasLimit, gasPriceString);
8684
}
8785

8886
// Convert to protobuf Fee

0 commit comments

Comments
 (0)