Skip to content

Commit

Permalink
Merge pull request #628 from irisdv/feat/support_starkname
Browse files Browse the repository at this point in the history
feat: support starkname
  • Loading branch information
lalalune authored Nov 28, 2024
2 parents e1fdeba + 3a67658 commit e5128f6
Show file tree
Hide file tree
Showing 4 changed files with 321 additions and 12 deletions.
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

0 comments on commit e5128f6

Please sign in to comment.