feat: initialize base agent node

This commit is contained in:
Jovian Dsouza 2025-05-02 00:19:41 +05:30
commit 2fbccb3423
13 changed files with 8082 additions and 536 deletions

View file

@ -0,0 +1,66 @@
import {
IAuthenticateGeneric,
ICredentialTestRequest,
ICredentialType,
INodeProperties,
} from 'n8n-workflow';
export class BaseApi implements ICredentialType {
name = 'baseApi';
displayName = 'Base API';
documentationUrl = 'https://docs.base.com/';
properties: INodeProperties[] = [
{
displayName: 'Private Key',
name: 'privateKey',
type: 'string',
typeOptions: {
password: true,
},
default: '',
required: true,
description: 'The private key of your Base wallet',
},
{
displayName: 'RPC URL',
name: 'rpcUrl',
type: 'string',
default: 'https://api.mainnet-beta.base.com',
required: true,
description: 'The URL of the Base RPC node',
},
{
displayName: 'OpenAI API Key',
name: 'openAiApiKey',
type: 'string',
typeOptions: {
password: true,
},
default: '',
required: true,
description: 'API key for OpenAI services',
},
];
authenticate: IAuthenticateGeneric = {
type: 'generic',
properties: {
headers: {
'Content-Type': 'application/json',
},
},
};
test: ICredentialTestRequest = {
request: {
baseURL: '={{$credentials.rpcUrl}}',
url: '/',
method: 'POST',
body: {
jsonrpc: '2.0',
id: 1,
method: 'getHealth',
},
},
};
}

View file

@ -1,56 +0,0 @@
import {
IAuthenticateGeneric,
ICredentialTestRequest,
ICredentialType,
INodeProperties,
} from 'n8n-workflow';
export class ExampleCredentialsApi implements ICredentialType {
name = 'exampleCredentialsApi';
displayName = 'Example Credentials API';
properties: INodeProperties[] = [
// The credentials to get from user and save encrypted.
// Properties can be defined exactly in the same way
// as node properties.
{
displayName: 'User Name',
name: 'username',
type: 'string',
default: '',
},
{
displayName: 'Password',
name: 'password',
type: 'string',
typeOptions: {
password: true,
},
default: '',
},
];
// This credential is currently not used by any node directly
// but the HTTP Request node can use it to make requests.
// The credential is also testable due to the `test` property below
authenticate: IAuthenticateGeneric = {
type: 'generic',
properties: {
auth: {
username: '={{ $credentials.username }}',
password: '={{ $credentials.password }}',
},
qs: {
// Send this as part of the query string
n8n: 'rocks',
},
},
};
// The block below tells how this credential can be tested
test: ICredentialTestRequest = {
request: {
baseURL: 'https://example.com/',
url: '',
},
};
}

View file

@ -1,50 +0,0 @@
import {
IAuthenticateGeneric,
ICredentialTestRequest,
ICredentialType,
INodeProperties,
} from 'n8n-workflow';
export class HttpBinApi implements ICredentialType {
name = 'httpbinApi';
displayName = 'HttpBin API';
documentationUrl = '<your-docs-url>';
properties: INodeProperties[] = [
{
displayName: 'Token',
name: 'token',
type: 'string',
default: '',
typeOptions: {
password: true,
}
},
{
displayName: 'Domain',
name: 'domain',
type: 'string',
default: 'https://httpbin.org',
},
];
// This allows the credential to be used by other parts of n8n
// stating how this credential is injected as part of the request
// An example is the Http Request node that can make generic calls
// reusing this credential
authenticate: IAuthenticateGeneric = {
type: 'generic',
properties: {
headers: {
Authorization: '={{"Bearer " + $credentials.token}}',
},
},
};
// The block below tells how this credential can be tested
test: ICredentialTestRequest = {
request: {
baseURL: '={{$credentials?.domain}}',
url: '/bearer',
},
};
}

View file

@ -0,0 +1,272 @@
import {
IExecuteFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
NodeOperationError,
} from 'n8n-workflow';
class BaseAgent implements INodeType {
description: INodeTypeDescription = {
displayName: 'Base Agent',
name: 'baseAgent',
icon: 'file:base.svg',
group: ['blockchain'],
version: 1,
subtitle: '={{$parameter["operation"]}}',
description: 'Interact with Base onchain AI agents',
defaults: {
name: 'Base Agent',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'baseApi',
required: true,
},
],
properties: [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
// eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
options: [
{
name: 'Create Token',
value: 'createToken',
description: 'Create a new token',
action: 'Create a new token',
},
{
name: 'Transfer Token',
value: 'transferToken',
description: 'Transfer tokens',
action: 'Transfer tokens',
},
{
name: 'Get Token Balances',
value: 'getTokenBalances',
description: 'Get all token balances for a wallet',
action: 'Get token balances',
},
{
name: 'Get Single Token Balance',
value: 'getSingleBalance',
description: 'Get balance for a specific token',
action: 'Get single token balance',
},
{
name: 'Get Other Wallet Balance',
value: 'getOtherBalance',
description: 'Get token balance for another wallet',
action: 'Get other wallet balance',
},
{
name: 'Close Empty Token Accounts',
value: 'closeEmptyTokenAccounts',
action: 'Close empty token accounts',
},
{
name: 'Request Faucet Funds',
value: 'requestFaucet',
description: 'Request SOL from faucet (devnet/testnet)',
action: 'Request faucet funds',
},
{
name: 'Get Network TPS',
value: 'getTPS',
description: 'Get current network TPS',
action: 'Get network TPS',
},
{
name: 'Get Wallet Address',
value: 'getWalletAddress',
description: 'Get current wallet address',
action: 'Get wallet address',
},
],
default: 'createToken',
},
// Token Creation Parameters
{
displayName: 'Token Name',
name: 'tokenName',
type: 'string',
typeOptions: { password: true },
required: true,
displayOptions: {
show: {
operation: ['createToken'],
},
},
default: '',
description: 'Name of the token to create',
},
{
displayName: 'Token Symbol',
name: 'tokenSymbol',
type: 'string',
typeOptions: { password: true },
required: true,
displayOptions: {
show: {
operation: ['createToken'],
},
},
default: '',
description: 'Symbol of the token to create',
},
{
displayName: 'Decimals',
name: 'decimals',
type: 'number',
displayOptions: {
show: {
operation: ['createToken'],
},
},
default: 9,
description: 'Number of decimal places for the token',
},
{
displayName: 'Initial Supply',
name: 'initialSupply',
type: 'number',
displayOptions: {
show: {
operation: ['createToken'],
},
},
default: 0,
description: 'Initial supply of tokens to mint',
},
// Transfer Parameters
{
displayName: 'Recipient Address',
name: 'recipientAddress',
type: 'string',
required: true,
displayOptions: {
show: {
operation: ['transferToken'],
},
},
default: '',
description: 'Recipient wallet address',
},
{
displayName: 'Amount',
name: 'amount',
type: 'number',
required: true,
displayOptions: {
show: {
operation: ['transferToken'],
},
},
default: 0,
description: 'Amount to transfer',
},
{
displayName: 'Token Address',
name: 'tokenAddress',
type: 'string',
typeOptions: { password: true },
displayOptions: {
show: {
operation: ['transferToken', 'getSingleBalance'],
},
},
default: '',
description: 'SPL token address (leave empty for SOL)',
},
// Balance Parameters
{
displayName: 'Wallet Address',
name: 'walletAddress',
type: 'string',
displayOptions: {
show: {
operation: ['getTokenBalances', 'getOtherBalance'],
},
},
default: '',
description: 'Wallet address to check balances for (leave empty for own wallet)',
},
{
displayName: 'Token Address for Other Wallet',
name: 'otherTokenAddress',
type: 'string',
typeOptions: { password: true },
displayOptions: {
show: {
operation: ['getOtherBalance'],
},
},
default: '',
description: 'Token address to check balance for (leave empty for SOL)',
},
],
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: INodeExecutionData[] = [];
const operation = this.getNodeParameter('operation', 0) as string;
// Get credentials
const credentials = await this.getCredentials('baseApi');
const rpcUrl = credentials.rpcUrl;
rpcUrl
// Initialize Wallet
// Initialize Base Agent Kit
for (let i = 0; i < items.length; i++) {
try {
let result;
switch (operation) {
//TODO: define base on chain actions
case 'transferToken':
// const recipientAddress = new PublicKey(this.getNodeParameter('recipientAddress', i) as string);
// const amount = this.getNodeParameter('amount', i) as number;
// const tokenAddress = this.getNodeParameter('tokenAddress', i) as string;
// const mintAddress = tokenAddress ? new PublicKey(tokenAddress) : undefined;
// result = await agent.methods.transfer(agent, recipientAddress, amount, mintAddress);
break;
default:
throw new NodeOperationError(this.getNode(), `The operation "${operation}" is not supported!`);
}
returnData.push({
json: {result},
});
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: {
error: error.message,
},
});
continue;
}
throw error;
}
}
return [returnData];
}
}
// Export the class
export { BaseAgent };

14
nodes/BaseAgent/base.svg Normal file
View file

@ -0,0 +1,14 @@
<svg width="46" height="46" viewBox="0 0 46 46" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_b_139_1266)">
<rect width="46" height="46" rx="23" fill="black" fill-opacity="0.9"/>
<path d="M22.9803 34.3703C29.2728 34.3703 34.3739 29.2796 34.3739 23.0001C34.3739 16.7205 29.2728 11.6299 22.9803 11.6299C17.0104 11.6299 12.1129 16.212 11.6265 22.0443H26.6861V23.9558H11.6265C12.1129 29.7882 17.0104 34.3703 22.9803 34.3703Z" fill="#E3E7E9"/>
</g>
<defs>
<filter id="filter0_b_139_1266" x="-14" y="-14" width="74" height="74" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feGaussianBlur in="BackgroundImageFix" stdDeviation="7"/>
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_139_1266"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_139_1266" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 939 B

View file

@ -1,76 +0,0 @@
import type {
IExecuteFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
export class ExampleNode implements INodeType {
description: INodeTypeDescription = {
displayName: 'Example Node',
name: 'exampleNode',
group: ['transform'],
version: 1,
description: 'Basic Example Node',
defaults: {
name: 'Example Node',
},
inputs: [NodeConnectionType.Main],
outputs: [NodeConnectionType.Main],
properties: [
// Node properties which the user gets displayed and
// can change on the node.
{
displayName: 'My String',
name: 'myString',
type: 'string',
default: '',
placeholder: 'Placeholder value',
description: 'The description text',
},
],
};
// The function below is responsible for actually doing whatever this node
// is supposed to do. In this case, we're just appending the `myString` property
// with whatever the user has entered.
// You can make async calls and use `await`.
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
let item: INodeExecutionData;
let myString: string;
// Iterates over all input items and add the key "myString" with the
// value the parameter "myString" resolves to.
// (This could be a different value for each item in case it contains an expression)
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
try {
myString = this.getNodeParameter('myString', itemIndex, '') as string;
item = items[itemIndex];
item.json.myString = myString;
} catch (error) {
// This node should never fail but we want to showcase how
// to handle errors.
if (this.continueOnFail()) {
items.push({ json: this.getInputData(itemIndex)[0].json, error, pairedItem: itemIndex });
} else {
// Adding `itemIndex` allows other workflows to handle this error
if (error.context) {
// If the error thrown already contains the context property,
// only append the itemIndex
error.context.itemIndex = itemIndex;
throw error;
}
throw new NodeOperationError(this.getNode(), error, {
itemIndex,
});
}
}
}
return [items];
}
}

View file

@ -1,18 +0,0 @@
{
"node": "n8n-nodes-base.httpbin",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Development", "Developer Tools"],
"resources": {
"credentialDocumentation": [
{
"url": "http://httpbin.org/#/Auth/get_bearer"
}
],
"primaryDocumentation": [
{
"url": "http://httpbin.org/"
}
]
}
}

View file

@ -1,62 +0,0 @@
import { INodeType, INodeTypeDescription } from 'n8n-workflow';
import { httpVerbFields, httpVerbOperations } from './HttpVerbDescription';
export class HttpBin implements INodeType {
description: INodeTypeDescription = {
displayName: 'HttpBin',
name: 'httpBin',
icon: 'file:httpbin.svg',
group: ['transform'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Interact with HttpBin API',
defaults: {
name: 'HttpBin',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'httpbinApi',
required: false,
},
],
requestDefaults: {
baseURL: 'https://httpbin.org',
url: '',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
},
/**
* In the properties array we have two mandatory options objects required
*
* [Resource & Operation]
*
* https://docs.n8n.io/integrations/creating-nodes/code/create-first-node/#resources-and-operations
*
* In our example, the operations are separated into their own file (HTTPVerbDescription.ts)
* to keep this class easy to read.
*
*/
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'HTTP Verb',
value: 'httpVerb',
},
],
default: 'httpVerb',
},
...httpVerbOperations,
...httpVerbFields,
],
};
}

View file

@ -1,248 +0,0 @@
import { INodeProperties } from 'n8n-workflow';
// When the resource `httpVerb` is selected, this `operation` parameter will be shown.
export const httpVerbOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['httpVerb'],
},
},
options: [
{
name: 'GET',
value: 'get',
description: 'Perform a GET request',
routing: {
request: {
method: 'GET',
url: '/get',
},
},
},
{
name: 'DELETE',
value: 'delete',
description: 'Perform a DELETE request',
routing: {
request: {
method: 'DELETE',
url: '/delete',
},
},
},
],
default: 'get',
},
];
// Here we define what to show when the `get` operation is selected.
// We do that by adding `operation: ["get"]` to `displayOptions.show`
const getOperation: INodeProperties[] = [
{
displayName: 'Type of Data',
name: 'typeofData',
default: 'queryParameter',
description: 'Select type of data to send [Query Parameters]',
displayOptions: {
show: {
resource: ['httpVerb'],
operation: ['get'],
},
},
type: 'options',
options: [
{
name: 'Query',
value: 'queryParameter',
},
],
required: true,
},
{
displayName: 'Query Parameters',
name: 'arguments',
default: {},
description: "The request's query parameters",
displayOptions: {
show: {
resource: ['httpVerb'],
operation: ['get'],
},
},
options: [
{
name: 'keyvalue',
displayName: 'Key:Value',
values: [
{
displayName: 'Key',
name: 'key',
type: 'string',
default: '',
required: true,
description: 'Key of query parameter',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
routing: {
send: {
property: '={{$parent.key}}',
type: 'query',
},
},
required: true,
description: 'Value of query parameter',
},
],
},
],
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
},
];
// Here we define what to show when the DELETE Operation is selected.
// We do that by adding `operation: ["delete"]` to `displayOptions.show`
const deleteOperation: INodeProperties[] = [
{
displayName: 'Type of Data',
name: 'typeofData',
default: 'queryParameter',
description: 'Select type of data to send [Query Parameter Arguments, JSON-Body]',
displayOptions: {
show: {
resource: ['httpVerb'],
operation: ['delete'],
},
},
options: [
{
name: 'Query',
value: 'queryParameter',
},
{
name: 'JSON',
value: 'jsonData',
},
],
required: true,
type: 'options',
},
{
displayName: 'Query Parameters',
name: 'arguments',
default: {},
description: "The request's query parameters",
displayOptions: {
show: {
resource: ['httpVerb'],
operation: ['delete'],
typeofData: ['queryParameter'],
},
},
options: [
{
name: 'keyvalue',
displayName: 'Key:Value',
values: [
{
displayName: 'Key',
name: 'key',
type: 'string',
default: '',
required: true,
description: 'Key of query parameter',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
routing: {
send: {
property: '={{$parent.key}}',
type: 'query',
},
},
required: true,
description: 'Value of query parameter',
},
],
},
],
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
},
{
displayName: 'JSON Object',
name: 'arguments',
default: {},
description: "The request's JSON properties",
displayOptions: {
show: {
resource: ['httpVerb'],
operation: ['delete'],
typeofData: ['jsonData'],
},
},
options: [
{
name: 'keyvalue',
displayName: 'Key:Value',
values: [
{
displayName: 'Key',
name: 'key',
type: 'string',
default: '',
required: true,
description: 'Key of JSON property',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
routing: {
send: {
property: '={{$parent.key}}',
type: 'body',
},
},
required: true,
description: 'Value of JSON property',
},
],
},
],
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
},
];
export const httpVerbFields: INodeProperties[] = [
/* -------------------------------------------------------------------------- */
/* httpVerb:get */
/* -------------------------------------------------------------------------- */
...getOperation,
/* -------------------------------------------------------------------------- */
/* httpVerb:delete */
/* -------------------------------------------------------------------------- */
...deleteOperation,
];

View file

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve"> <image id="image0" width="32" height="32" x="0" y="0"
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAABGdBTUEAALGPC/xhBQAAACBjSFJN
AAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAHdElN
RQfmBg4UAC/TqOZZAAACA0lEQVRIx5XVv09TURwF8M+jFHDSyRkGFhPAEfyRdDHi5uriXyDoYgKT
MJDWzUT/Ahf/AiOEpajEgCESmpiYmDCxGowDTYE+h76+vte+15Zzk753b7733HNO772PbEw7ECba
genswtEcgl0/PHARV72066YrIDSZ6k8KBym4741r0XsB284TdUX8chn1zrzwJUmw4KFXPqjFE0Y0
u5YKEhpmfLZuy7f2wLKGI8WhDRYdaVhurdTCidmU5P44N+skaaGQH1IfFFrOYMotT932zNgQExve
OfTeT8dtBceO3TFlOyopY7UPxV+/fWyn3Y0xrFhJjZWFXhs12pKdRO9ObGSuyB8Xbd9JjMjDc6HQ
IcrKqAiVe8vyCEJPrGBWxZYqqtZt9RbmHabAvAAVdVUlJTvWshbMt0AYn40OmlchSKOePTyYIMQn
rb8yI8TsDCrRs4od7Jv3KOoPGWKboBqp2LN3FQvdO7EPshSsRSTXrSop2cSiiUGkG/bj2JqaQiHW
4nv50mFcu28j30KQarAnEPhuzvwwGYQ975vx7+JwGXTjTIAzoYlhCArR5d0KkfauqJAVY6+FG5hD
OS6veqyCuSiTAQT/jKmlQtyxIBCoZV28HQvN6LuQvJFC4xjvibfYOZUdUXd9taTWJbOubiIVXmjG
W/fs9qpZcpr6pOe1U0udSf8BR7ef4yxyOskAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjItMDYtMTRU
MTc6MDA6NDcrMDM6MDBfo1sRAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIyLTA2LTE0VDE3OjAwOjQ3
KzAzOjAwLv7jrQAAAABJRU5ErkJggg==" />
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

7723
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -36,12 +36,10 @@
"n8n": {
"n8nNodesApiVersion": 1,
"credentials": [
"dist/credentials/ExampleCredentialsApi.credentials.js",
"dist/credentials/HttpBinApi.credentials.js"
"dist/credentials/BaseApi.credentials.js"
],
"nodes": [
"dist/nodes/ExampleNode/ExampleNode.node.js",
"dist/nodes/HttpBin/HttpBin.node.js"
"dist/nodes/BaseAgent/BaseAgent.node.js"
]
},
"devDependencies": {

9
pnpm-lock.yaml generated
View file

@ -7,6 +7,10 @@ settings:
importers:
.:
dependencies:
n8n-workflow:
specifier: '*'
version: 1.48.0
devDependencies:
'@typescript-eslint/parser':
specifier: ^7.15.0
@ -20,9 +24,6 @@ importers:
gulp:
specifier: ^4.0.2
version: 4.0.2
n8n-workflow:
specifier: '*'
version: 1.48.0
prettier:
specifier: ^3.3.2
version: 3.3.2
@ -758,7 +759,7 @@ packages:
resolution: {integrity: sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==}
engines: {node: '>= 4.0'}
os: [darwin]
deprecated: The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2
deprecated: Upgrade to fsevents v2 to mitigate potential security issues
function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}