your ecosystem.
The ability to send transactions between Solana, Ethereum, Cosmos and DotSama, leveraging IBC connections, built by Picasso.
<aside> <img src="/icons/arrows-swap-vertically_gray.svg" alt="/icons/arrows-swap-vertically_gray.svg" width="40px" /> Link your protocol to Picasso to unlock IBC everywhere.
Wether you have a bridge, or another protocol, that wants to smoothly allow their users to send their assets via IBC between ecosystems, you are on the right page.
</aside>
<aside> <img src="/icons/snippet_orange.svg" alt="/icons/snippet_orange.svg" width="40px" /> Our SDK repo
</aside>
<aside> <img src="/icons/alien-pixel_gray.svg" alt="/icons/alien-pixel_gray.svg" width="40px" /> This is the full SDK, that allows you to use Picasso ecosystem. Following this docs will allow you to perform cross-chain sends through our IBC connections among:
<aside> <img src="/icons/snippet_gray.svg" alt="/icons/snippet_gray.svg" width="40px" /> You will be able to add these transfers to your frontend with the help of SDK and track these transactions with the help of our indexer API.
Check our repo with SDK first
https://github.com/ComposableFi/picasso-sdk/tree/main
</aside>
Install SDK:
npm install react graphql graphql-tag subscriptions-transport-ws picasso-sdk
note: To use the indexer API, you need to obtain the Hasura endpoint and secret key. Please contact the Picasso team for assistance.
Use PicassoStatus.ts.
import { useEffect, useState } from 'react';
import { type DocumentNode } from 'graphql';
import gql from 'graphql-tag';
import { type IbcEventsResponse } from 'picasso-sdk';
import { SubscriptionClient } from 'subscriptions-transport-ws';
type QueryKey = {
fromBlockHash?: { _eq: string };
sequence?: { _eq: number };
};
const HASURA_GRAPHQL_ENDPOINT = process.env.NEXT_PUBLIC_HASURA_URL || '';
const HASURA_ADMIN_SECRET = process.env.NEXT_PUBLIC_HASURA_PRIVATE_KEY || '';
const subscriptionQueryWithTxHash = gql`
subscription MySubscription(
$txHash: String!
$fromBlockHash: String_comparison_exp = {}
$sequence: String_comparison_exp = {}
) {
IbcEvents(where: { data: { _contains: { txHash: $txHash } }, fromBlockHash: $fromBlockHash, sequence: $sequence }) {
data
fromAssetId
fromAmount
fromAddress
fromBlockHash
fromChainId
fromFee
fromFeeAssetId
fromTimestamp
nextSequence
sequence
sourceChannel
status
timeout
toAddress
toAmount
toAssetId
toBlockHash
toChainId
toFee
toFeeAssetId
updatedAt
toTimestamp
type
timeout_height
}
}
`;
const subscriptionQueryWithoutTxHash = gql`
subscription MySubscription($fromBlockHash: String_comparison_exp = {}, $sequence: String_comparison_exp = {}) {
IbcEvents(where: { fromBlockHash: $fromBlockHash, sequence: $sequence }) {
data
fromAssetId
fromAmount
fromAddress
fromBlockHash
fromChainId
fromFee
fromFeeAssetId
fromTimestamp
nextSequence
sequence
sourceChannel
status
timeout
toAddress
toAmount
toAssetId
toBlockHash
toChainId
toFee
toFeeAssetId
updatedAt
toTimestamp
type
timeout_height
}
}
`;
export const usePicassoStatus = (txHash?: string, duration: number = 10000) => {
const [ibcEvent, setIbcEvent] = useState<Partial<IbcEventsResponse>>();
const [hopIndex, setHopIndex] = useState(-1);
const resetStatus = () => {
setIbcEvent(undefined);
setHopIndex(-1);
};
const subscribeToIbcEvents = (
client: SubscriptionClient,
variables: QueryKey & { txHash?: string },
subscriptionQuery: DocumentNode
) => {
if (hopIndex > 100) {
client.close();
return;
}
const subscription = client.request({ query: subscriptionQuery, variables }).subscribe({
next(data) {
console.log('Received data:', data);
const event = data?.data?.IbcEvents?.[0];
setIbcEvent(event);
if (event?.fromBlockHash !== ibcEvent?.fromBlockHash) {
setHopIndex(prev => prev + 1);
}
if (event?.toBlockHash && event?.nextSequence) {
const nextVariables = {
fromBlockHash: { _eq: event.toBlockHash },
sequence: { _eq: event.nextSequence }
};
subscribeToIbcEvents(client, nextVariables, subscriptionQueryWithoutTxHash);
} else if (event && ['TransferPending', 'send_packet'].every(v => event?.status !== v)) {
client.close();
console.log('Subscription stopped:', event);
}
},
error(err) {
console.error('Subscription error:', err);
},
complete() {
console.log('Subscription complete');
}
});
return subscription;
};
useEffect(() => {
if (!txHash) return;
resetStatus();
const client = new SubscriptionClient(
HASURA_GRAPHQL_ENDPOINT,
{
reconnect: true,
connectionParams: {
headers: {
'x-hasura-admin-secret': HASURA_ADMIN_SECRET
}
}
},
WebSocket
);
const initialVariables = { txHash };
const initialSubscription = subscribeToIbcEvents(client, initialVariables, subscriptionQueryWithTxHash);
if (ibcEvent && ibcEvent?.status !== 'TransferPending' && ibcEvent?.status !== 'send_packet') {
console.log('this has closed');
client.close();
initialSubscription?.unsubscribe();
const timer = setTimeout(() => {
hopIndex >= 0 && setHopIndex(-1);
setIbcEvent(undefined);
}, duration);
return () => {
clearTimeout(timer);
console.log('Subscription stopped!');
};
}
return () => {
initialSubscription?.unsubscribe();
client.close();
clearTimeout(duration);
};
}, [txHash, duration]);
return { hopIndex, ibcEvent, resetStatus };
};
Example usage:
const Stepper = () => {
const { ibcEvent, hopIndex, resetStatus } = usePicassoStatus('txHash...');
return <div>stepper..</div>;
};