Skip to content

Commit 820edb8

Browse files
committed
added accountFromAny
1 parent 329b461 commit 820edb8

File tree

5 files changed

+497
-39
lines changed

5 files changed

+497
-39
lines changed

networks/cosmos/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,12 @@
5050
"test:rpc:v2": "jest --config ./jest.rpc.config.js --testPathPattern=query-client-v2.test.ts --verbose"
5151
},
5252
"dependencies": {
53+
"@interchainjs/amino": "1.17.5",
5354
"@interchainjs/auth": "1.17.5",
5455
"@interchainjs/cosmos-types": "1.17.5",
5556
"@interchainjs/crypto": "1.17.5",
5657
"@interchainjs/encoding": "1.17.5",
58+
"@interchainjs/pubkey": "1.17.5",
5759
"@interchainjs/types": "1.17.5",
5860
"@interchainjs/utils": "1.17.5",
5961
"@noble/curves": "^1.1.0",

networks/cosmos/src/query/cosmos-query-client.ts

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,10 @@ import { ValidatorsParams } from '../types/requests/common/validators';
4848
import { BroadcastTxParams } from '../types/requests/common/tx';
4949
import { GenesisChunkedParams } from '../types/requests/common/genesis-chunked';
5050
import { ICosmosProtocolAdapter } from '../adapters/base';
51-
import { BaseAccount, BinaryReader } from '@interchainjs/cosmos-types';
51+
import { BaseAccount } from '@interchainjs/cosmos-types';
5252
import { getAccount } from '@interchainjs/cosmos-types';
53+
import { accountFromAny } from '../utils';
54+
import { encodePubkey } from '@interchainjs/pubkey';
5355

5456

5557

@@ -338,34 +340,16 @@ export class CosmosQueryClient implements ICosmosQueryClient {
338340
return null;
339341
}
340342

341-
const { typeUrl, value } = response.account;
343+
// Use the new accountFromAny function to parse the account
344+
const account = accountFromAny(response.account);
342345

343-
// If it's a BaseAccount, decode it directly
344-
if (typeUrl === '/cosmos.auth.v1beta1.BaseAccount') {
345-
return BaseAccount.decode(value);
346-
}
347-
348-
// For other account types, decode the first field as BaseAccount
349-
// This pattern applies to vesting accounts and other wrapper types
350-
const reader = new BinaryReader(value);
351-
let baseAccount: BaseAccount | null = null;
352-
353-
// Read the first field (tag 1) as BaseAccount
354-
while (reader.pos < reader.len) {
355-
const tag = reader.uint32();
356-
const fieldNumber = tag >>> 3;
357-
358-
if (fieldNumber === 1) {
359-
// First field should be BaseAccount
360-
baseAccount = BaseAccount.decode(reader, reader.uint32());
361-
break;
362-
} else {
363-
// Skip other fields
364-
reader.skipType(tag & 7);
365-
}
366-
}
367-
368-
return baseAccount;
346+
// Convert the standardized Account back to BaseAccount format
347+
return {
348+
address: account.address,
349+
pubKey: account.pubkey ? encodePubkey(account.pubkey) : undefined,
350+
accountNumber: BigInt(account.accountNumber),
351+
sequence: BigInt(account.sequence),
352+
};
369353
} catch (error) {
370354
console.warn(`Failed to get base account for address ${address}:`, error);
371355
return null;

networks/cosmos/src/utils.test.ts

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
import { accountFromAny, Account } from "./utils";
2+
import {
3+
BaseAccount,
4+
ModuleAccount,
5+
ContinuousVestingAccount,
6+
DelayedVestingAccount,
7+
PeriodicVestingAccount,
8+
PermanentLockedAccount,
9+
Any,
10+
BaseVestingAccount,
11+
} from "@interchainjs/cosmos-types";
12+
import { encodePubkey } from "@interchainjs/pubkey";
13+
import { encodeSecp256k1Pubkey } from "@interchainjs/amino";
14+
15+
describe("accountFromAny", () => {
16+
const testAddress = "cosmos1abc123def456ghi789jkl012mno345pqr678st";
17+
18+
// Create a valid compressed secp256k1 pubkey (33 bytes starting with 0x02 or 0x03)
19+
const validPubkeyBytes = new Uint8Array(33);
20+
validPubkeyBytes[0] = 0x02; // Compressed pubkey prefix
21+
for (let i = 1; i < 33; i++) {
22+
validPubkeyBytes[i] = i % 256;
23+
}
24+
const testPubkey = encodeSecp256k1Pubkey(validPubkeyBytes);
25+
const testAccountNumber = BigInt(42);
26+
const testSequence = BigInt(7);
27+
28+
describe("BaseAccount", () => {
29+
it("should decode a BaseAccount correctly", () => {
30+
const baseAccount: BaseAccount = {
31+
address: testAddress,
32+
pubKey: encodePubkey(testPubkey),
33+
accountNumber: testAccountNumber,
34+
sequence: testSequence,
35+
};
36+
37+
const accountAny: Any = {
38+
typeUrl: "/cosmos.auth.v1beta1.BaseAccount",
39+
value: BaseAccount.encode(baseAccount).finish(),
40+
};
41+
42+
const result = accountFromAny(accountAny);
43+
44+
expect(result).toEqual({
45+
address: testAddress,
46+
pubkey: testPubkey,
47+
accountNumber: 42,
48+
sequence: 7,
49+
});
50+
});
51+
52+
it("should handle BaseAccount with no pubkey", () => {
53+
const baseAccount: BaseAccount = {
54+
address: testAddress,
55+
pubKey: undefined,
56+
accountNumber: testAccountNumber,
57+
sequence: testSequence,
58+
};
59+
60+
const accountAny: Any = {
61+
typeUrl: "/cosmos.auth.v1beta1.BaseAccount",
62+
value: BaseAccount.encode(baseAccount).finish(),
63+
};
64+
65+
const result = accountFromAny(accountAny);
66+
67+
expect(result).toEqual({
68+
address: testAddress,
69+
pubkey: null,
70+
accountNumber: 42,
71+
sequence: 7,
72+
});
73+
});
74+
});
75+
76+
describe("Wrapper account types using binary parsing", () => {
77+
it("should decode a ModuleAccount using binary parsing", () => {
78+
const baseAccount: BaseAccount = {
79+
address: testAddress,
80+
pubKey: encodePubkey(testPubkey),
81+
accountNumber: testAccountNumber,
82+
sequence: testSequence,
83+
};
84+
85+
const moduleAccount: ModuleAccount = {
86+
baseAccount,
87+
name: "test-module",
88+
permissions: ["mint", "burn"],
89+
};
90+
91+
const accountAny: Any = {
92+
typeUrl: "/cosmos.auth.v1beta1.ModuleAccount",
93+
value: ModuleAccount.encode(moduleAccount).finish(),
94+
};
95+
96+
const result = accountFromAny(accountAny);
97+
98+
expect(result).toEqual({
99+
address: testAddress,
100+
pubkey: testPubkey,
101+
accountNumber: 42,
102+
sequence: 7,
103+
});
104+
});
105+
106+
it("should decode a ContinuousVestingAccount using enhanced binary parsing", () => {
107+
const baseAccount: BaseAccount = {
108+
address: testAddress,
109+
pubKey: encodePubkey(testPubkey),
110+
accountNumber: testAccountNumber,
111+
sequence: testSequence,
112+
};
113+
114+
const baseVestingAccount: BaseVestingAccount = {
115+
baseAccount,
116+
originalVesting: [],
117+
delegatedFree: [],
118+
delegatedVesting: [],
119+
endTime: BigInt(1000000),
120+
};
121+
122+
const vestingAccount: ContinuousVestingAccount = {
123+
baseVestingAccount,
124+
startTime: BigInt(500000),
125+
};
126+
127+
const accountAny: Any = {
128+
typeUrl: "/cosmos.vesting.v1beta1.ContinuousVestingAccount",
129+
value: ContinuousVestingAccount.encode(vestingAccount).finish(),
130+
};
131+
132+
const result = accountFromAny(accountAny);
133+
134+
expect(result).toEqual({
135+
address: testAddress,
136+
pubkey: testPubkey,
137+
accountNumber: 42,
138+
sequence: 7,
139+
});
140+
});
141+
142+
it("should decode a DelayedVestingAccount using enhanced binary parsing", () => {
143+
const baseAccount: BaseAccount = {
144+
address: testAddress,
145+
pubKey: encodePubkey(testPubkey),
146+
accountNumber: testAccountNumber,
147+
sequence: testSequence,
148+
};
149+
150+
const baseVestingAccount: BaseVestingAccount = {
151+
baseAccount,
152+
originalVesting: [],
153+
delegatedFree: [],
154+
delegatedVesting: [],
155+
endTime: BigInt(2000000),
156+
};
157+
158+
const vestingAccount: DelayedVestingAccount = {
159+
baseVestingAccount,
160+
};
161+
162+
const accountAny: Any = {
163+
typeUrl: "/cosmos.vesting.v1beta1.DelayedVestingAccount",
164+
value: DelayedVestingAccount.encode(vestingAccount).finish(),
165+
};
166+
167+
const result = accountFromAny(accountAny);
168+
169+
expect(result).toEqual({
170+
address: testAddress,
171+
pubkey: testPubkey,
172+
accountNumber: 42,
173+
sequence: 7,
174+
});
175+
});
176+
177+
it("should decode a PeriodicVestingAccount using enhanced binary parsing", () => {
178+
const baseAccount: BaseAccount = {
179+
address: testAddress,
180+
pubKey: encodePubkey(testPubkey),
181+
accountNumber: testAccountNumber,
182+
sequence: testSequence,
183+
};
184+
185+
const baseVestingAccount: BaseVestingAccount = {
186+
baseAccount,
187+
originalVesting: [],
188+
delegatedFree: [],
189+
delegatedVesting: [],
190+
endTime: BigInt(3000000),
191+
};
192+
193+
const vestingAccount: PeriodicVestingAccount = {
194+
baseVestingAccount,
195+
startTime: BigInt(1500000),
196+
vestingPeriods: [],
197+
};
198+
199+
const accountAny: Any = {
200+
typeUrl: "/cosmos.vesting.v1beta1.PeriodicVestingAccount",
201+
value: PeriodicVestingAccount.encode(vestingAccount).finish(),
202+
};
203+
204+
const result = accountFromAny(accountAny);
205+
206+
expect(result).toEqual({
207+
address: testAddress,
208+
pubkey: testPubkey,
209+
accountNumber: 42,
210+
sequence: 7,
211+
});
212+
});
213+
214+
it("should decode a PermanentLockedAccount using enhanced binary parsing", () => {
215+
const baseAccount: BaseAccount = {
216+
address: testAddress,
217+
pubKey: encodePubkey(testPubkey),
218+
accountNumber: testAccountNumber,
219+
sequence: testSequence,
220+
};
221+
222+
const baseVestingAccount: BaseVestingAccount = {
223+
baseAccount,
224+
originalVesting: [],
225+
delegatedFree: [],
226+
delegatedVesting: [],
227+
endTime: BigInt(4000000),
228+
};
229+
230+
const vestingAccount: PermanentLockedAccount = {
231+
baseVestingAccount,
232+
};
233+
234+
const accountAny: Any = {
235+
typeUrl: "/cosmos.vesting.v1beta1.PermanentLockedAccount",
236+
value: PermanentLockedAccount.encode(vestingAccount).finish(),
237+
};
238+
239+
const result = accountFromAny(accountAny);
240+
241+
expect(result).toEqual({
242+
address: testAddress,
243+
pubkey: testPubkey,
244+
accountNumber: 42,
245+
sequence: 7,
246+
});
247+
});
248+
249+
it("should handle unknown account types that follow BaseAccount pattern", () => {
250+
// Create a BaseAccount and encode it
251+
const baseAccount: BaseAccount = {
252+
address: "cosmos1test123unknown456type789",
253+
pubKey: encodePubkey(testPubkey),
254+
accountNumber: BigInt(999),
255+
sequence: BigInt(123),
256+
};
257+
258+
// Create a mock wrapper account that has BaseAccount as first field (tag 1)
259+
const baseAccountBytes = BaseAccount.encode(baseAccount).finish();
260+
261+
// Create a protobuf message with BaseAccount as field 1
262+
const mockWrapperBytes = new Uint8Array([
263+
0x0a, // tag 1, wire type 2 (length-delimited)
264+
baseAccountBytes.length, // length of BaseAccount
265+
...baseAccountBytes,
266+
0x12, 0x04, 0x74, 0x65, 0x73, 0x74, // tag 2, some additional field "test"
267+
]);
268+
269+
const accountAny: Any = {
270+
typeUrl: "/unknown.wrapper.CustomAccountType",
271+
value: mockWrapperBytes,
272+
};
273+
274+
const result = accountFromAny(accountAny);
275+
276+
expect(result).toEqual({
277+
address: "cosmos1test123unknown456type789",
278+
pubkey: testPubkey,
279+
accountNumber: 999,
280+
sequence: 123,
281+
});
282+
});
283+
});
284+
285+
describe("Error handling", () => {
286+
it("should throw error for unsupported account type", () => {
287+
const accountAny: Any = {
288+
typeUrl: "/unknown.account.Type",
289+
value: new Uint8Array(),
290+
};
291+
292+
expect(() => accountFromAny(accountAny)).toThrow(
293+
"Unsupported account type: /unknown.account.Type"
294+
);
295+
});
296+
});
297+
});

0 commit comments

Comments
 (0)