Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
zCollectionResource,
zMoveViewResponse,
zNftResource,
zNodeInfo,
zObjectCoreResource,
zRegistries,
zValidator,
Expand Down Expand Up @@ -59,6 +60,33 @@ export class ApiClient {
return z.string().parse(response);
}

public async findIbcCounterPartyChainId(
chainId: string,
portId: string,
channelId: string
): Promise<string | null> {
await this._getRegistries();

const registry = this.registries.find(
(registry) => registry.chain_id === chainId
);

if (!registry) {
return null;
}

const channel = registry.metadata.ibc_channels.find(
(channel) =>
channel.port_id === portId && channel.channel_id === channelId
);

if (!channel) {
return null;
}

return channel.chain_id;
}

public async findNftFromTokenAddr(
tokenAddr: string
): Promise<NftResource | null> {
Expand Down Expand Up @@ -114,6 +142,18 @@ export class ApiClient {
}
}

public async getChainId(): Promise<string> {
try {
const response = await axios.get(
`${this.restUrl}/cosmos/base/tendermint/v1beta1/node_info`
);
return zNodeInfo.parse(response.data).default_node_info.network;
} catch (error) {
console.error("Failed to fetch chain ID:", error);
throw new Error("Unable to retrieve chain ID from node info");
}
}

private async _getAccountResources(
address: string
): Promise<AccountResource[] | null> {
Expand Down
66 changes: 62 additions & 4 deletions src/decoders/ibc/nft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,31 @@ export const ibcSendNftDecoder: MessageDecoder = {
);
}

const srcChainId = await apiClient.getChainId();

const dstPort = event.attributes.find(
(attr) => attr.key === "packet_dst_port"
)?.value;
if (!dstPort) {
throw new Error("IBC NFT Send packet dst port attribute not found");
}

const dstChannel = event.attributes.find(
(attr) => attr.key === "packet_dst_channel"
)?.value;
if (!dstChannel) {
throw new Error("IBC NFT Send packet dst channel attribute not found");
}

const dstChainId = await apiClient.findIbcCounterPartyChainId(
srcChainId,
source_port,
source_channel
);
if (!dstChainId) {
throw new Error("IBC NFT Send packet dst chain id not found");
}

return {
action: "ibc_nft_send",
data: {
Expand All @@ -59,10 +84,14 @@ export const ibcSendNftDecoder: MessageDecoder = {
uri: collection.data.uri || parsedData.data.classUri,
},
collectionId: toBech32(denomToHex(class_id)),
dstChainId,
dstChannel,
dstPort,
receiver,
sender,
sourceChannel: source_channel,
sourcePort: source_port,
srcChainId,
srcChannel: source_channel,
srcPort: source_port,
tokenIds: parsedData.data.tokenIds,
tokenUris: parsedData.data.tokenUris,
},
Expand Down Expand Up @@ -125,6 +154,31 @@ export const ibcReceiveNftDecoder: MessageDecoder = {
);
}

const dstChainId = await apiClient.getChainId();

const srcPort = event.attributes.find(
(attr) => attr.key === "packet_src_port"
)?.value;
if (!srcPort) {
throw new Error("IBC NFT Receive packet src port attribute not found");
}

const srcChannel = event.attributes.find(
(attr) => attr.key === "packet_src_channel"
)?.value;
if (!srcChannel) {
throw new Error("IBC NFT Receive packet src channel attribute not found");
}

const srcChainId = await apiClient.findIbcCounterPartyChainId(
dstChainId,
destination_port,
destination_channel
);
if (!srcChainId) {
throw new Error("IBC NFT Receive packet src chain id not found");
}

return {
action: "ibc_nft_receive",
data: {
Expand All @@ -135,10 +189,14 @@ export const ibcReceiveNftDecoder: MessageDecoder = {
uri: collection.data.uri || parsedData.data.classUri,
},
collectionId: toBech32(collection_id),
destinationChannel: destination_channel,
destinationPort: destination_port,
dstChainId,
dstChannel: destination_channel,
dstPort: destination_port,
receiver: parsedData.data.receiver,
sender: parsedData.data.sender,
srcChainId,
srcChannel,
srcPort,
tokenIds: parsedData.data.tokenIds,
tokenUris: parsedData.data.tokenUris,
},
Expand Down
56 changes: 42 additions & 14 deletions src/decoders/ibc/transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,46 @@ export const ibcSendFtDecoder: MessageDecoder = {
const parsed = zMsgIbcTransfer.safeParse(message);
return parsed.success && parsed.data.source_port === "transfer";
},
decode: async (message: Message, log: Log, _apiClient: ApiClient) => {
decode: async (message: Message, log: Log, apiClient: ApiClient) => {
const parsed = zMsgIbcTransfer.parse(message);
const { receiver, sender, source_channel, source_port, token } = parsed;

const event = log.events.find((event) => event.type === "send_packet");

if (!event) throw new Error("IBC Transfer send packet event not found");

const dstChannel = event.attributes.find(
(attr) => attr.key === "packet_dst_channel"
)?.value;
if (!dstChannel) throw new Error(`IBC Transfer dst channel not found`);

const dstPort = event.attributes.find(
(attr) => attr.key === "packet_dst_port"
)?.value;
if (!dstPort) throw new Error(`IBC Transfer dst port not found`);

const srcChainId = await apiClient.getChainId();

const dstChainId = await apiClient.findIbcCounterPartyChainId(
srcChainId,
source_port,
source_channel
);
if (!dstChainId) throw new Error(`IBC Transfer dst chain id not found`);

return {
action: "ibc_ft_send",
data: {
amount: token.amount,
denom: token.denom,
destinationChannel:
event.attributes.find((attr) => attr.key === "packet_dst_channel")
?.value ?? "",
destinationPort:
event.attributes.find((attr) => attr.key === "packet_dst_port")
?.value ?? "",
dstChainId,
dstChannel,
dstPort,
receiver,
sender,
sourceChannel: source_channel,
sourcePort: source_port,
srcChainId,
srcChannel: source_channel,
srcPort: source_port,
},
isIbc: true,
isOp: false,
Expand All @@ -48,7 +65,7 @@ export const ibcReceiveFtDecoder: MessageDecoder = {
const parsed = zMsgIbcRecvPacket.safeParse(message);
return parsed.success && parsed.data.packet.source_port === "transfer";
},
decode: async (message: Message, _log: Log, _apiClient: ApiClient) => {
decode: async (message: Message, _log: Log, apiClient: ApiClient) => {
const parsed = zMsgIbcRecvPacket.parse(message);
const {
packet: {
Expand All @@ -64,17 +81,28 @@ export const ibcReceiveFtDecoder: MessageDecoder = {
Buffer.from(data, "base64").toString()
);

const dstChainId = await apiClient.getChainId();

const srcChainId = await apiClient.findIbcCounterPartyChainId(
dstChainId,
destination_port,
destination_channel
);
if (!srcChainId) throw new Error(`IBC Transfer src chain id not found`);

return {
action: "ibc_ft_receive",
data: {
amount: parsedPacket.amount,
denom: parsedPacket.denom,
destinationChannel: destination_channel,
destinationPort: destination_port,
dstChainId,
dstChannel: destination_channel,
dstPort: destination_port,
receiver: parsedPacket.receiver,
sender: parsedPacket.sender,
sourceChannel: source_channel,
sourcePort: source_port,
srcChainId,
srcChannel: source_channel,
srcPort: source_port,
},
isIbc: true,
isOp: false,
Expand Down
36 changes: 24 additions & 12 deletions src/interfaces/decoded-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,14 @@ interface DecodedIbcFtSendMessage extends DecodedMessageBase {
data: {
amount: string;
denom: string;
destinationChannel: string;
destinationPort: string;
dstChainId: string;
dstChannel: string;
dstPort: string;
receiver: string;
sender: string;
sourceChannel: string;
sourcePort: string;
srcChainId: string;
srcChannel: string;
srcPort: string;
};
}

Expand All @@ -54,12 +56,14 @@ interface DecodedIbcFtReceiveMessage extends DecodedMessageBase {
data: {
amount: string;
denom: string;
destinationChannel: string;
destinationPort: string;
dstChainId: string;
dstChannel: string;
dstPort: string;
receiver: string;
sender: string;
sourceChannel: string;
sourcePort: string;
srcChainId: string;
srcChannel: string;
srcPort: string;
};
}

Expand Down Expand Up @@ -190,10 +194,14 @@ interface DecodedIbcNftSendMessage extends DecodedMessageBase {
uri: string | null;
};
collectionId: string;
dstChainId: string;
dstChannel: string;
dstPort: string;
receiver: string;
sender: string;
sourceChannel: string;
sourcePort: string;
srcChainId: string;
srcChannel: string;
srcPort: string;
tokenIds: string[];
tokenUris: string[];
};
Expand All @@ -209,10 +217,14 @@ interface DecodedIbcNftReceiveMessage extends DecodedMessageBase {
uri: string | null;
};
collectionId: string;
destinationChannel: string;
destinationPort: string;
dstChainId: string;
dstChannel: string;
dstPort: string;
receiver: string;
sender: string;
srcChainId: string;
srcChannel: string;
srcPort: string;
tokenIds: string[];
tokenUris: string[];
};
Expand Down
1 change: 1 addition & 0 deletions src/schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from "./messages";
export * from "./packet";
export * from "./registry";
export * from "./resources";
export * from "./rest";
export * from "./transaction";
export * from "./validators";
export * from "./view";
9 changes: 9 additions & 0 deletions src/schema/registry.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import { z } from "zod";

const zIbcChannel = z.object({
chain_id: z.string(),
channel_id: z.string(),
port_id: z.string(),
version: z.string(),
});

const zMetadata = z.object({
ibc_channels: z.array(zIbcChannel),
op_bridge_id: z.string().optional(),
op_denoms: z.array(z.string()).optional(),
});

export const zRegistry = z.object({
chain_id: z.string(),
chain_name: z.string(),

metadata: zMetadata,
});
export type Registry = z.infer<typeof zRegistry>;
Expand Down
7 changes: 7 additions & 0 deletions src/schema/rest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { z } from "zod";

export const zNodeInfo = z.object({
default_node_info: z.object({
network: z.string(),
}),
});
Loading