Group 1251.png

Picasso becomes a part of

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>

get in touch

SDK.

<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>

Getting Started

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.

Indexer API

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>;
};

Solana