Skip to content

Update zerion extension #18165

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
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
5 changes: 5 additions & 0 deletions extensions/zerion/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Zerion Changelog

## [Zerion AI Extension] - {PR_MERGE_DATE}
- Analyze token stats, info and historical prices
- Analyze wallet portfolio
- Generate links to Trade and Send forms with the netural language

## [Zerion Client] - 2024-12-20
- View wallet portfolio
- Search tokens, addresses or domains
Expand Down
Binary file added extensions/zerion/media/zerion-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1,283 changes: 746 additions & 537 deletions extensions/zerion/package-lock.json

Large diffs are not rendered by default.

159 changes: 156 additions & 3 deletions extensions/zerion/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,161 @@
"mode": "no-view"
}
],
"tools": [
{
"name": "look-wallets",
"title": "Check Saved Wallets",
"description": "Get the list of saved wallets"
},
{
"name": "check-portfolio",
"title": "Fetch Wallet Portfolio",
"description": "Fetch the portfolio and all positions of a wallet"
},
{
"name": "look-token",
"title": "Fetch Token Info",
"description": "Fetch the info of a token by the tokenId. This will bring information about the token, its price, its presence on different l2 chains and its market cap"
},
{
"name": "search-blockchain",
"title": "Search Token Info",
"description": "Search token short info by the symbol, name or the address. This tool will return the list of the best matches. It is usually better to use the first item from the list"
},
{
"name": "check-token-price-chart",
"title": "Get Token Historical Price Data",
"description": "Get the historical price data of a token by the tokenId. This will bring the price data of the token in the requested period"
},
{
"name": "create-a-swap-link",
"title": "Generating Trade Link",
"description": "Generate a trade link to Zerion Web App in case user wants to buy, sell a token or swap 2 tokens."
},
{
"name": "create-a-send-link",
"title": "Generating Send Link",
"description": "Generate a send link to Zerion Web App in case user wants to send a token to another wallet."
}
],
"ai": {
"instructions": "All prices are in USD. If there is a send request and ENS domain provided, just pass it as is without extra search. If there is a send or swap request and only token name or symbol provided, search for the token info first and pass the address to the tool.",
"evals": [
{
"input": "@zerion What is the price of eth",
"mocks": {
"search-blockchain": [
{
"symbol": "ETH",
"id": "eth",
"meta": {
"price": 2016.25,
"relativeChange1d": -2.7788493066136972
},
"name": "Ethereum"
},
{
"symbol": "WETH",
"id": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
"meta": {
"price": 2015.0536983875,
"relativeChange1d": -2.7625533377850577
},
"name": "Wrapped Ethereum"
}
]
},
"expected": [
{
"callsTool": "search-blockchain"
},
{
"meetsCriteria": "shows 2016.25 formatted in some way"
}
]
},
{
"input": "@zerion Send 200 USDT to test.zerion.eth",
"mocks": {
"search-blockchain": [
{
"symbol": "USDC",
"id": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"name": "USD Coin"
},
{
"symbol": "USDT",
"id": "0xdac17f958d2ee523a2206206994597c13d831ec7",
"name": "Tether USD"
}
],
"create-a-send-link": {}
},
"expected": [
{
"callsTool": {
"name": "search-blockchain",
"arguments": {
"query": "USDT"
}
}
},
{
"callsTool": {
"name": "create-a-send-link",
"arguments": {
"sendAmount": 200,
"sendTokenId": "0xdac17f958d2ee523a2206206994597c13d831ec7",
"recipient": "test.zerion.eth"
}
}
}
]
},
{
"input": "@zerion What is the largest asset held at test.zerion.eth",
"mocks": {
"check-portfolio": {
"positions": [
{
"value": 0.0016226387441992428,
"symbol": "TUSD",
"id": "0x0000000000085d4780b73119b644ae5ecd22b376",
"name": "TrueUSD"
},
{
"value": 0.0033353848492271997,
"symbol": "USDC",
"id": "1469f757-c583-4bab-a91f-bb2221e7069f",
"name": "USD Coin (Fantom)"
},
{
"value": 0.000005180155450957471,
"symbol": "HIGHER",
"id": "5b175bb6-5130-42c6-97e2-f352b1578b0a",
"name": "higher"
}
]
}
},
"expected": [
{
"callsTool": {
"name": "check-portfolio",
"arguments": {
"addressOrDomain": "test.zerion.eth"
}
}
},
{
"meetsCriteria": "shows USD Coin (Fantom) or USDC formatted in some way"
}
]
}
]
},
"dependencies": {
"@raycast/api": "^1.86.1",
"@raycast/api": "^1.94.2",
"@raycast/utils": "^1.18.1",
"bignumber.js": "^9.1.2",
"dayjs": "^1.11.13",
Expand All @@ -140,7 +293,7 @@
"@raycast/eslint-config": "^1.0.6",
"@types/lodash": "^4.14.202",
"@types/node": "20.8.10",
"@types/react": "18.2.27",
"@types/react": "^19.0.10",
"@types/uuid": "^9.0.7",
"eslint": "^8.51.0",
"prettier": "^3.0.3",
Expand All @@ -153,4 +306,4 @@
"lint": "ray lint",
"publish": "npx @raycast/api@latest publish"
}
}
}
10 changes: 9 additions & 1 deletion extensions/zerion/src/shared/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import groupBy from "lodash/groupBy";
import type { AggregatedPosition, Position } from "./types";
import { ellipsis } from "./typography";
import { LocalStorage } from "@raycast/api";
import { LocalStorage, showToast, Toast } from "@raycast/api";
import BigNumber from "bignumber.js";
import { ADDRESSES_KEY, MENU_BAR_ADDRESS_KEY } from "./constants";
import type { NormalizedAddress } from "./NormalizedAddress";
Expand Down Expand Up @@ -159,3 +159,11 @@ export function formatWithSignificantValue(value: number) {
const [significantValue, symbol] = getSignificantValue(value);
return `${Math.floor(significantValue)}${symbol}`;
}

export async function handleError({ error, title }: { error: unknown; title: string }) {
return showToast({
style: Toast.Style.Failure,
title: title,
message: error instanceof Error ? error.message : "",
});
}
72 changes: 72 additions & 0 deletions extensions/zerion/src/tools/check-portfolio.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { getZpiHeaders, ZPI_URL } from "../shared/api";
import { normalizeAddress } from "../shared/NormalizedAddress";
import { AddressPortfolio, Position } from "../shared/types";
import { handleError } from "../shared/utils";

type Input = {
/**
* Ethereum address or ENS domain to look up
*/
addressOrDomain: string;
};

export default async function (input: Input): Promise<{ portfolio?: AddressPortfolio; positions?: Position[] }> {
let address: string | null = null;

try {
const response = await fetch(`${ZPI_URL}wallet/get-meta/v1?identifiers=${input.addressOrDomain}`, {
headers: getZpiHeaders(),
});
const result = await response.json();
address = normalizeAddress(result.data[0]?.address);
} catch (error) {
handleError({ title: "Failed to fetch wallet", error });
return {};
}

let portfolio: { data: AddressPortfolio } | null = null;

try {
const portfolioResponse = await fetch(`${ZPI_URL}wallet/get-portfolio/v1`, {
method: "POST",
headers: getZpiHeaders({ "Zerion-Wallet-Provider": "Watch Address" }),
body: JSON.stringify({
addresses: [address],
currency: "usd",
nftPriceType: "not_included",
}),
});
portfolio = await portfolioResponse.json();
} catch (error) {
handleError({ title: "Failed to fetch wallet portfolio", error });
return {};
}

let cleanedPositions: Position[] = [];
try {
const positionsResponse = await fetch(`${ZPI_URL}wallet/get-positions/v1`, {
method: "POST",
headers: getZpiHeaders({ "Zerion-Wallet-Provider": "Watch Address" }),
body: JSON.stringify({
addresses: [address],
currency: "usd",
}),
});
const positions = await positionsResponse.json();
cleanedPositions = positions.data.map((position: Position) => {
return {
value: position.value,
chain: position.chain,
name: position.asset.name,
symbol: position.asset.symbol,
id: position.asset.id,
};
});
} catch (error) {
handleError({ title: "Failed to fetch wallet positions", error });
}
return {
portfolio: portfolio?.data,
positions: cleanedPositions,
};
}
43 changes: 43 additions & 0 deletions extensions/zerion/src/tools/check-token-price-chart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { getZpiHeaders, ZPI_URL } from "../shared/api";
import { handleError } from "../shared/utils";

interface Input {
/**
* Ethereum address of the token
* required parameter
* example: 0x6b175474e89094c44da98b954eedeac495271d0f
*/
tokenId: string;
/**
* Time period until now that we want to check the price points
* optional parameter
* check the last year price by default
*/
period?: "1h" | "1d" | "1w" | "1m" | "1y" | "max";
}

/**
* this tool returns the historical price of a token as an array of pair [timestamp, price]
*/
export default async function (input: Input): Promise<[string, number][]> {
try {
const response = await fetch(`${ZPI_URL}asset/get-fungible-chart/v1`, {
method: "POST",
headers: getZpiHeaders(),
body: JSON.stringify({
fungibleId: input.tokenId,
currency: "usd",
addresses: [],
period: input.period,
}),
});
const chartPointsRaw = await response.json();
return chartPointsRaw.data.points.map((item: { timestamp: string; value: number; extra: null }) => [
item.timestamp,
item.value,
]);
} catch (error) {
handleError({ title: "Failed to fetch token price chart", error });
return [];
}
}
45 changes: 45 additions & 0 deletions extensions/zerion/src/tools/create-a-send-link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { open } from "@raycast/api";
import { handleError } from "../shared/utils";

interface Input {
/**
* Chain ID of the network
* example: ethereum, polygon, zero
*/
chain?: string;
/**
* Token ID of the token to send
* it should not be a token name or symbol!
* example: 0x6b175474e89094c44da98b954eedeac495271d0f or c582638a-e7c3-45b9-ac7e-1e7295b822b5
*/
sendTokenId?: string;
/**
* Amount to send
* example: 100
*/
sendAmount?: number;
/**
* Ethereum address or ens domain to receive the token
*/
recipient?: string;
}

/**
* Generate and open a link to send some tokens to a recepient on Zerion
* you can pass recepient as is from the request
* in case some params are not given, leave them empty
*/
export default async function (input: Input) {
const urlParams = new URLSearchParams({
tokenChain: input.chain || "",
addressInputValue: input.recipient || "",
tokenAssetCode: input.sendTokenId || "",
tokenValue: input.sendAmount?.toString() || "",
});
const link = `https://app.zerion.io/send?${urlParams.toString()}`;
try {
await open(link);
} catch (error) {
await handleError({ title: "Failed to open Zerion Send Form", error });
}
}
Loading
Loading