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: support starkname #628

Merged
merged 3 commits into from
Nov 28, 2024
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
189 changes: 189 additions & 0 deletions packages/plugin-starknet/src/actions/subdomain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// It should just transfer subdomain from the root domain owned by the agent's wallet to the recipient.

import {
ActionExample,
HandlerCallback,
IAgentRuntime,
Memory,
ModelClass,
State,
type Action,
composeContext,
generateObject,
Content,
elizaLogger,
} from "@ai16z/eliza";
import { getStarknetAccount } from "../utils";
import { validateStarknetConfig } from "../enviroment";
import { getTransferSubdomainCall, isStarkDomain } from "../utils/starknetId";

export interface SubdomainCreationContent extends Content {
recipient: string;
subdomain: string;
}

export function isSubdomainCreation(
content: SubdomainCreationContent
): content is SubdomainCreationContent {
// Validate types
const validTypes =
typeof content.recipient === "string" &&
typeof content.subdomain === "string";
if (!validTypes) {
return false;
}

// Validate recipient (must be 32-bytes long with 0x prefix)
const validTokenAddress =
content.recipient.startsWith("0x") && content.recipient.length === 66;
if (!validTokenAddress) {
return false;
}

// Validate subdomain
const validStarkName =
isStarkDomain(content.subdomain) &&
content.subdomain.split(".").length === 3;

if (!validStarkName) {
return false;
}
return true;
}

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
{
"recipient": "0x1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF",
"subdomain": "subdomain.domain.stark",
}
\`\`\`

{{recentMessages}}

Given the recent messages, extract the following information about the requested subdomain creation:
- Subdomain to create
- Recipient wallet address


Respond with a JSON markdown block containing only the extracted values.`;

export default {
name: "CREATE_SUBDOMAIN",
similes: [
"CREATE_SUBDOMAIN_ON_STARKNET",
"SUBDOMAIN_ON_STARKNET",
"SUBDOMAIN_CREATION",
"SEND_SUBDOMAIN_ON_STARKNET",
],
validate: async (runtime: IAgentRuntime, _message: Memory) => {
await validateStarknetConfig(runtime);
return true;
},
description:
"MUST use this action if the user requests create a subdomain, the request might be varied, but it will always be a subdomain creation.",
handler: async (
runtime: IAgentRuntime,
message: Memory,
state: State,
_options: { [key: string]: unknown },
callback?: HandlerCallback
): Promise<boolean> => {
elizaLogger.log("Starting CREATE_SUBDOMAIN handler...");

// Initialize or update state
if (!state) {
state = (await runtime.composeState(message)) as State;
} else {
state = await runtime.updateRecentMessageState(state);
}

// Compose transfer context
const transferContext = composeContext({
state,
template: transferTemplate,
});

// Generate transfer content
const content = await generateObject({
runtime,
context: transferContext,
modelClass: ModelClass.MEDIUM,
});

elizaLogger.debug("Transfer content:", content);

// Validate transfer content
if (!isSubdomainCreation(content)) {
elizaLogger.error("Invalid content for CREATE_SUBDOMAIN action.");
if (callback) {
callback({
text: "Not enough information to create subdomain. Please respond with your domain and the subdomain to create.",
content: { error: "Invalid subdomain creation content" },
});
}
return false;
}

try {
const account = getStarknetAccount(runtime);

const transferCall = getTransferSubdomainCall(
account.address,
content.subdomain,
content.recipient
);

elizaLogger.success(
"Transferring",
content.subdomain,
"to",
content.recipient
);

const tx = await account.execute(transferCall);

elizaLogger.success(
"Transfer completed successfully! tx: " + tx.transaction_hash
);
if (callback) {
callback({
text:
"Transfer completed successfully! tx: " +
tx.transaction_hash,
content: {},
});
}

return true;
} catch (error) {
elizaLogger.error("Error during subdomain transfer:", error);
if (callback) {
callback({
text: `Error transferring subdomain ${content.subdomain}: ${error.message}`,
content: { error: error.message },
});
}
return false;
}
},

examples: [
[
{
user: "{{user1}}",
content: {
text: "Send me subdomain.domain.stark to 0x0124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49",
},
},
{
user: "{{agent}}",
content: {
text: "I'll transfer subdomain.domain.stark to that address right away. Let me process that for you.",
},
},
],
] as ActionExample[][],
} as Action;
90 changes: 79 additions & 11 deletions packages/plugin-starknet/src/actions/transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ import {
import { getStarknetAccount } from "../utils";
import { ERC20Token } from "../utils/ERC20Token";
import { validateStarknetConfig } from "../enviroment";
import { getAddressFromName, isStarkDomain } from "../utils/starknetId";

export interface TransferContent extends Content {
tokenAddress: string;
recipient: string;
recipient?: string;
starkName?: string;
amount: string | number;
}

Expand All @@ -30,21 +32,40 @@ export function isTransferContent(
// Validate types
const validTypes =
typeof content.tokenAddress === "string" &&
typeof content.recipient === "string" &&
(typeof content.recipient === "string" ||
typeof content.starkName === "string") &&
(typeof content.amount === "string" ||
typeof content.amount === "number");
if (!validTypes) {
return false;
}

// Validate addresses (must be 32-bytes long with 0x prefix)
const validAddresses =
// Validate tokenAddress (must be 32-bytes long with 0x prefix)
const validTokenAddress =
content.tokenAddress.startsWith("0x") &&
content.tokenAddress.length === 66 &&
content.recipient.startsWith("0x") &&
content.recipient.length === 66;
content.tokenAddress.length === 66;
if (!validTokenAddress) {
return false;
}

return validAddresses;
// Additional checks based on whether recipient or starkName is defined
if (content.recipient) {
// Validate recipient address (must be 32-bytes long with 0x prefix)
const validRecipient =
content.recipient.startsWith("0x") &&
content.recipient.length === 66;
if (!validRecipient) {
return false;
}
} else if (content.starkName) {
// .stark name validation
const validStarkName = isStarkDomain(content.starkName);
if (!validStarkName) {
return false;
}
}

return true;
}

const transferTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.
Expand All @@ -62,6 +83,7 @@ Example response:
{
"tokenAddress": "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
"recipient": "0x1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF",
"starkName": "domain.stark",
"amount": "0.001"
}
\`\`\`
Expand All @@ -71,6 +93,7 @@ Example response:
Given the recent messages, extract the following information about the requested token transfer:
- Token contract address
- Recipient wallet address
- Recipient .stark name


Respond with a JSON markdown block containing only the extracted values.`;
Expand Down Expand Up @@ -126,7 +149,7 @@ export default {
elizaLogger.error("Invalid content for TRANSFER_TOKEN action.");
if (callback) {
callback({
text: "Not enough information to transfer tokens. Please respond with token address, recipient, and amount.",
text: "Not enough information to transfer tokens. Please respond with token address, recipient address or stark name, and amount.",
content: { error: "Invalid transfer content" },
});
}
Expand All @@ -142,8 +165,11 @@ export default {
Number(content.amount) * Math.pow(10, Number(decimals))
);
const amountWei = BigInt(amountInteger.toString());
const recipient =
content.recipient ??
(await getAddressFromName(account, content.starkName));
const transferCall = erc20Token.transferCall(
content.recipient,
recipient,
amountWei
);

Expand All @@ -153,7 +179,7 @@ export default {
"of",
content.tokenAddress,
"to",
content.recipient
recipient
);

const tx = await account.execute(transferCall);
Expand Down Expand Up @@ -198,6 +224,20 @@ export default {
},
},
],
[
{
user: "{{user1}}",
content: {
text: "Send 10 ETH to domain.stark",
},
},
{
user: "{{agent}}",
content: {
text: "I'll transfer 10 ETH to domain.stark et address 0x0124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49 right away. Let me process that for you.",
},
},
],
[
{
user: "{{user1}}",
Expand All @@ -212,6 +252,20 @@ export default {
},
},
],
[
{
user: "{{user1}}",
content: {
text: "Can you transfer 50 LORDS tokens to domain.stark?",
},
},
{
user: "{{agent}}",
content: {
text: "Executing transfer of 50 LORDS tokens to domain.stark at address 0x0124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49. One moment please.",
},
},
],
[
{
user: "{{user1}}",
Expand All @@ -226,5 +280,19 @@ export default {
},
},
],
[
{
user: "{{user1}}",
content: {
text: "Please send 0.5 BTC to domain.stark",
},
},
{
user: "{{agent}}",
content: {
text: "Got it, initiating transfer of 0.5 BTC to domain.stark at address 0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac. I'll confirm once it's complete.",
},
},
],
] as ActionExample[][],
} as Action;
3 changes: 2 additions & 1 deletion packages/plugin-starknet/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Plugin } from "@ai16z/eliza";
import { executeSwap } from "./actions/swap";
import transfer from "./actions/transfer";
import { deployToken } from "./actions/unruggable";
import transferSubdomain from "./actions/subdomain";
export const PROVIDER_CONFIG = {
AVNU_API: "https://starknet.impulse.avnu.fi/v1",
MAX_RETRIES: 3,
Expand All @@ -20,7 +21,7 @@ export const PROVIDER_CONFIG = {
export const starknetPlugin: Plugin = {
name: "starknet",
description: "Starknet Plugin for Eliza",
actions: [transfer, executeSwap, deployToken],
actions: [transfer, executeSwap, deployToken, transferSubdomain],
evaluators: [],
providers: [],
};
Expand Down
Loading
Loading