Skip to content

Commit

Permalink
fix(0x): fix swap issue (#30)
Browse files Browse the repository at this point in the history
* fix(0x): fix swap issue

* build: freeze lockfile

* build: fix debug commands

* fix(coinbase): wrong argument to handle approval function

* docs(coinbase): add test commands

* fix(coinbase): convert amount to buy/sell to whole number
  • Loading branch information
snobbee authored Feb 18, 2025
1 parent 5d09916 commit 9a66605
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 75 deletions.
2 changes: 2 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node-linker=hoisted
frozen-lockfile=true
30 changes: 28 additions & 2 deletions clients/client-coinbase/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,32 @@

Coinbase client for ElizaOS that handles trading signals via webhooks.

## Configuration
## Testing

Required environment variables:
You can test the Coinbase client webhook endpoints using the following curl commands:

### Test Buy Signal

```bash
curl -X POST http://localhost:3000/webhook/coinbase/94518332-6d84-0df9-b71b-3de37d3976a5 \
-H "Content-Type: application/json" \
-d "{
\"event\": \"buy\",
\"ticker\": \"BTC\",
\"timestamp\": $(python3 -c 'import time; print(int(time.time() * 1000))'),
\"price\": $(curl -s -X GET "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd" -H "accept: application/json" | jq .bitcoin.usd)
}"
```

### Test Sell Signal

```bash
curl -X POST http://localhost:3000/webhook/coinbase/94518332-6d84-0df9-b71b-3de37d3976a5 \
-H "Content-Type: application/json" \
-d "{
\"event\": \"sell\",
\"ticker\": \"BTC\",
\"timestamp\": $(python3 -c 'import time; print(int(time.time() * 1000))'),
\"price\": $(curl -s -X GET "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd" -H "accept: application/json" | jq .bitcoin.usd)
}"
```
40 changes: 14 additions & 26 deletions clients/client-coinbase/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { Coinbase, Wallet } from "@coinbase/coinbase-sdk";
import { Coinbase } from "@coinbase/coinbase-sdk";
import {
type Client,
Content,
HandlerCallback,
type IAgentRuntime,
type Memory,
ModelClass,
Expand All @@ -27,15 +25,10 @@ import {
} from "@realityspiral/plugin-coinbase";
import { postTweet } from "@realityspiral/plugin-twitter";
import express from "express";
import {
http,
createWalletClient,
erc20Abi,
formatUnits,
publicActions,
} from "viem";
import { http, createWalletClient, erc20Abi, publicActions } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { base } from "viem/chains";
import { getTokenMetadata } from "../../../plugins/plugin-0x/src/utils";
import {
type WebhookEvent,
blockExplorerBaseAddressUrl,
Expand All @@ -51,19 +44,11 @@ export type WalletType =
| "dry_powder"
| "operational_capital";

const TOKEN_DECIMALS: { [key: string]: number } = {
USDC: 6,
ETH: 18,
BTC: 8,
CBBTC: 18,
};

export class CoinbaseClient implements Client {
private runtime: IAgentRuntime;
private server: express.Application;
private port: number;
private wallets: CoinbaseWallet[];
private initialBalanceETH: number;

constructor(runtime: IAgentRuntime) {
this.runtime = runtime;
Expand All @@ -75,7 +60,6 @@ export class CoinbaseClient implements Client {
this.server = express();
this.port = Number(runtime.getSetting("COINBASE_WEBHOOK_PORT")) || 3001;
this.wallets = [];
this.initialBalanceETH = 1;
}

async initialize(): Promise<void> {
Expand Down Expand Up @@ -299,12 +283,16 @@ Generate only the tweet text, no commentary or markdown.`;

// Execute token swap
const buy = event.event.toUpperCase() === "BUY";
const tokenDecimals = TOKEN_DECIMALS[event.ticker] || 18; // Default to 18 if not found
const usdcDecimals = TOKEN_DECIMALS.USDC; // 6 decimals for USDC

const amountInCurrency = buy
? amount * 10 ** usdcDecimals // Convert USD amount to USDC base units
: (amount / Number(event.price)) * 10 ** tokenDecimals; // Convert to token base units
const tokenMetadata = getTokenMetadata(event.ticker);
const usdcMetadata = getTokenMetadata("USDC");
const tokenDecimals = tokenMetadata?.decimals || 18; // Default to 18 if not found
const usdcDecimals = usdcMetadata?.decimals || 6; // Default to 6 if not found

const amountInCurrency = Math.floor(
buy
? amount * 10 ** usdcDecimals // Convert USD amount to USDC base units
: (amount / Number(event.price)) * 10 ** tokenDecimals, // Convert to token base units
);
elizaLogger.info(
"amountInCurrency non base units ",
amount / Number(event.price),
Expand Down Expand Up @@ -564,7 +552,6 @@ export async function getTotalBalanceUSD(
"base-mainnet",
erc20Abi,
);
elizaLogger.info(`cbbtcBalanceBaseUnits ${cbbtcBalanceBaseUnits}`);
const cbbtcPriceInquiry = await getPriceInquiry(
runtime,
"CBBTC",
Expand All @@ -578,6 +565,7 @@ export async function getTotalBalanceUSD(
}
const cbbtcQuote = await getQuoteObj(runtime, cbbtcPriceInquiry, publicKey);
const cbbtcBalanceUSD = Number(cbbtcQuote.buyAmount) / 1000000;
elizaLogger.info(`cbbtcBalanceUSD ${cbbtcBalanceUSD}`);
return ethBalanceUSD + usdcBalance + cbbtcBalanceUSD;
}

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
"scripts": {
"build": "turbo build",
"dev": "turbo --concurrency 100 dev",
"clean:dev": "rm -f agent/data/db.sqlite && turbo dev",
"clean:dev": "if [ -f agent/data/db.sqlite ]; then rm agent/data/db.sqlite; fi && turbo --concurrency 100 dev",
"start": "turbo start",
"clean:start": "rm -f agent/data/db.sqlite && turbo start",
"clean:start": "if [ -f agent/data/db.sqlite ]; then rm agent/data/db.sqlite; fi && turbo start",
"lint": "turbo lint",
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
"smokeTests": "bash ./scripts/smoke-tests.sh",
Expand Down
40 changes: 2 additions & 38 deletions plugins/plugin-0x/src/actions/getIndicativePrice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,7 @@ import {
elizaLogger,
generateObject,
} from "@elizaos/core";
import {
http,
createWalletClient,
erc20Abi,
getContract,
maxUint256,
parseUnits,
publicActions,
} from "viem";
import { http, createWalletClient, parseUnits, publicActions } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { base } from "viem/chains";
import { z } from "zod";
Expand All @@ -31,7 +23,7 @@ import {
type GetIndicativePriceResponse,
type PriceInquiry,
} from "../types";
import { TOKENS } from "../utils";
import { getTokenMetadata } from "../utils";

export const IndicativePriceSchema = z.object({
sellTokenSymbol: z.string().nullable(),
Expand Down Expand Up @@ -304,26 +296,6 @@ export const storePriceInquiryToMemory = async (
await memoryManager.createMemory(memory);
};

const getTokenMetadata = (tokenSymbol: string) => {
switch (tokenSymbol) {
case "ETH":
return TOKENS.ETH;
case "WETH":
return TOKENS.WETH;
case "USDC":
return TOKENS.USDC;
case "CBBTC":
case "BTC":
case "WBTC":
return TOKENS.cbBTC;
case "DAI":
return TOKENS.DAI;
default:
elizaLogger.error(`${tokenSymbol} is not supported`);
return null;
}
};

export const getPriceInquiry = async (
runtime: IAgentRuntime,
sellTokenSymbol: string,
Expand Down Expand Up @@ -382,14 +354,6 @@ export const getPriceInquiry = async (

if (!price) return null;

// Handle token approvals
// const approved = await handleTokenApprovals(
// client,
// price,
// sellTokenMetadata.address as `0x${string}`,
// );
// if (!approved) return null;

// Format response
const formattedAmounts = formatAmounts(
price,
Expand Down
12 changes: 6 additions & 6 deletions plugins/plugin-0x/src/actions/swap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ export const tokenSwap = async (
}

const chainId = Chains.base;
elizaLogger.info("chainId ", chainId);
elizaLogger.info(`chainId ${chainId}`);
let quote = null;
attempt = 0;

Expand Down Expand Up @@ -280,8 +280,8 @@ export const tokenSwap = async (
// Handle token approvals
const approved = await handleTokenApprovals(
client,
priceInquiry,
priceInquiry.sellTokenMetadata.address as `0x${string}`,
quote,
priceInquiry.sellTokenObject.address as `0x${string}`,
);
elizaLogger.info("approved ", approved);
if (!approved) return null;
Expand Down Expand Up @@ -351,7 +351,7 @@ export const tokenSwap = async (
const handleTokenApprovals = async (
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
client: any,
price: GetIndicativePriceResponse,
quote: GetIndicativePriceResponse,
sellTokenAddress: `0x${string}` = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
): Promise<boolean> => {
try {
Expand All @@ -362,11 +362,11 @@ const handleTokenApprovals = async (
client: client as any,
});

if (price.issues.allowance !== null) {
if (quote.issues.allowance !== null) {
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
const { request } = await (sellTokenContract as any).simulate.approve([
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
(price as any).issues.allowance.spender,
(quote as any).issues.allowance.spender,
maxUint256,
]);

Expand Down
22 changes: 21 additions & 1 deletion plugins/plugin-0x/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IAgentRuntime } from "@elizaos/core";
import { IAgentRuntime, elizaLogger } from "@elizaos/core";
import { Hash, formatUnits } from "viem";
import { EVMTokenRegistry } from "./EVMtokenRegistry";

Expand Down Expand Up @@ -91,3 +91,23 @@ export const TOKENS = {
pairs: [],
},
};

export const getTokenMetadata = (tokenSymbol: string) => {
switch (tokenSymbol) {
case "ETH":
return TOKENS.ETH;
case "WETH":
return TOKENS.WETH;
case "USDC":
return TOKENS.USDC;
case "CBBTC":
case "BTC":
case "WBTC":
return TOKENS.cbBTC;
case "DAI":
return TOKENS.DAI;
default:
elizaLogger.error(`${tokenSymbol} is not supported`);
return null;
}
};

0 comments on commit 9a66605

Please sign in to comment.