Skip to content
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

feat: starknet token transfer #373

Merged
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
4 changes: 2 additions & 2 deletions packages/plugin-solana/src/actions/transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ export default {
/*
const adminIds = runtime.getSetting("ADMIN_USER_IDS")?.split(",") || [];
//console.log("Admin IDs from settings:", adminIds);
const isAdmin = adminIds.includes(message.userId);
if (isAdmin) {
//console.log(`Authorized transfer from user: ${message.userId}`);
return true;
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-starknet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"@ai16z/eliza": "workspace:*",
"@ai16z/plugin-trustdb": "workspace:*",
"@avnu/avnu-sdk": "^2.1.1",
"starknet": "^6.11.0",
"starknet": "^6.17.0",
"tsup": "^8.3.5",
"vitest": "^2.1.4"
},
Expand Down
48 changes: 41 additions & 7 deletions packages/plugin-starknet/src/actions/transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ import {
composeContext,
generateObject,
} from "@ai16z/eliza";
import { validateSettings } from "../utils";
import { getStarknetAccount, validateSettings } from "../utils";
import { Account, RpcProvider } from "starknet";
import { PROVIDER_CONFIG } from "..";
import path from "path";
import fs from "fs";
import { ERC20Token } from "../utils/ERC20Token";

export interface TransferContent extends Content {
tokenAddress: string;
Expand All @@ -27,22 +32,34 @@ function isTransferContent(
content: TransferContent
): content is TransferContent {
console.log("Content for transfer", content);
return (
const validTypes = (
typeof content.tokenAddress === "string" &&
typeof content.recipient === "string" &&
(typeof content.amount === "string" ||
typeof content.amount === "number")
);
if (!validTypes) {
return false;
}

// Addresses must be 32-bytes long with a 0x prefix
const validAddresses = (
content.tokenAddress.startsWith("0x") &&
content.tokenAddress.length === 66 &&
content.recipient.startsWith("0x") &&
content.recipient.length === 66
);
return validTypes && validAddresses;
}

const transferTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.

Example response:
\`\`\`json
{
"tokenAddress": "BieefG47jAHCGZBxi2q87RDuHyGZyYC3vAzxpyu8pump",
"recipient": "9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa",
"amount": "1000"
"tokenAddress": "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
"recipient": "0x1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF",
"amount": "0.001"
}
\`\`\`

Expand Down Expand Up @@ -110,9 +127,20 @@ export default {
}

try {
const account = getStarknetAccount(runtime);
const erc20Token = new ERC20Token(content.tokenAddress, account);
const decimals = await erc20Token.decimals();
const amountWei = BigInt(content.amount) * 10n ** BigInt(decimals);
const transferCall = erc20Token.transferCall(content.recipient, amountWei);

console.log("Transferring", amountWei, "of", content.tokenAddress, "to", content.recipient);

const tx = await account.execute(transferCall);

console.log("Transfer completed successfully! tx: " + tx.transaction_hash);
if (callback) {
callback({
text: `Successfully`,
text: "Transfer completed successfully! tx: " + tx.transaction_hash,
content: {},
});
}
Expand All @@ -135,7 +163,13 @@ export default {
{
user: "{{user1}}",
content: {
text: "Send 69 STRK to 0x1234567890123456789012345678901234567890",
text: "Send 69 STRK to 0x1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF",
},
},
{
user: "{{user2}}",
content: {
text: "Transfer to 0x1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF 0.01 ETH",
},
},
],
Expand Down
9 changes: 4 additions & 5 deletions packages/plugin-starknet/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@ export const PROVIDER_CONFIG = {
AVNU_API: "https://starknet.impulse.avnu.fi/v1",
MAX_RETRIES: 3,
RETRY_DELAY: 2000,
DEFAULT_RPC: "https://api.mainnet-beta.solana.com",
DEFAULT_RPC: "https://free-rpc.nethermind.io/mainnet-juno/",
TOKEN_ADDRESSES: {
SOL: "",
BTC: "",
ETH: "",
STRK: "",
BTC: "0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac",
ETH: "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
STRK: "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d",
},
TOKEN_SECURITY_ENDPOINT: "/defi/token_security?address=",
TOKEN_TRADE_DATA_ENDPOINT: "/defi/v3/token/trade-data/single?address=",
Expand Down
59 changes: 59 additions & 0 deletions packages/plugin-starknet/src/utils/ERC20Token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import path from "path";
import fs from "fs";
import { Account, Call, CallData, Calldata, Contract, cairo } from "starknet";

export type ApproveCall = {
contractAddress: string;
entrypoint: "approve";
calldata: Calldata;
};

export type TransferCall = {
contractAddress: string;
entrypoint: "transfer";
calldata: Calldata;
};

export class ERC20Token {
abi: any;
contract: Contract;
calldata: CallData;
constructor(token: string, account?: Account) {
const erc20AbiPath = path.join(__dirname, "../utils/erc20.json");
const erc20Abi = JSON.parse(fs.readFileSync(erc20AbiPath, "utf8"));
this.contract = new Contract(erc20Abi, token, account);
this.calldata = new CallData(this.contract.abi);
}

public address() {
return this.contract.address;
}

public async decimals() {
const result = await this.contract.call("decimals");
return result as bigint;
}


public approveCall(spender: string, amount: bigint): ApproveCall {
return {
contractAddress: this.contract.address,
entrypoint: "approve",
calldata: this.calldata.compile("approve", {
spender: spender,
amount: cairo.uint256(amount),
}),
};
}

public transferCall(recipient: string, amount: bigint): TransferCall {
return {
contractAddress: this.contract.address,
entrypoint: "transfer",
calldata: this.calldata.compile("transfer", {
recipient: recipient,
amount: cairo.uint256(amount),
}),
};
}
}
Loading