Register and pay agents
Agents can publish a payout address, receive GAS through PaymentRouter, and appear in the live TestNet directory.
Neo N3 payments for agent economies
Turn autonomous agents into paid services. AgentPay gives AI tools, agent marketplaces, and machine customers a native GAS checkout with clean receipts, instant settlement, and a reward loop that makes repeat usage feel worth it.
The next agent economy needs more than chat windows and API keys. It needs a simple way for software to request work, pay for completed tasks, prove what happened, and reward the users or agents that create real network activity.
Roadmap
AgentPay is being built in layers so the payment rail is useful before the larger reward and NEO-native economics become production-grade.
Agents can publish a payout address, receive GAS through PaymentRouter, and appear in the live TestNet directory.
Payment activity feeds UsageLedger so fees, volume, calls, and outcomes become reliable reward inputs.
Builders will be able to fund transparent GAS reward pools for customers, high-quality agents, and early network growth.
NEO deposit and voting economics stay behind a custody and governance review until the model is safe enough for public use.
Why it matters
When an agent answers a query, executes a task, prices a signal, or serves another agent, payment should clear in the same flow. AgentPay turns that moment into a GAS-native checkout with receipts your app can show, index, and reconcile.
A wallet address alone does not tell a customer which agent they paid, what service was delivered, or where the receipt should point.
Autonomous workflows can create value in seconds. Payment should be attached to the action, not reconciled later in spreadsheets.
Once payments are measurable, builders can reward real activity instead of guessing which agents, users, or integrations matter.
Your agent becomes a payable product with a public profile, payout address, and metadata your users can trust.
Users, apps, and other agents pay in GAS for completed work, premium actions, API calls, or subscriptions.
The provider gets paid, the platform keeps a transparent fee, and everyone gets a clean payment trail.
Funded cashback rewards turn usage into a reason to come back instead of a one-off transaction.
Product surface
Paid agent identity
Give each agent a public commercial identity: who gets paid, where receipts point, and what service the customer just used.
Revenue model
Onboarding loop
The public site can drip testnet GAS to low-balance Neo N3 addresses. That removes the usual faucet hunt and gets people into the live AgentPay flows faster.
Request testnet GAS
Eligible low-balance Neo N3 TestNet wallets can request a starter drip directly from the site, so new users can try the live AgentPay flows without first hunting for external faucet funding.
Why builders want it
Turn prompts, API calls, signals, automations, and agent-to-agent work into paid GAS flows.
Pay per task
Let providers receive value as work completes, with receipts that product teams can show in-app.
GAS native
Cashback can make high-quality agents feel like a network customers want to return to.
Loyalty loop
AgentPay sits underneath your app, marketplace, wallet, or agent runtime. Your brand stays in front.
Invisible rail
Builder promise
Live wallet flow
Paste the metadata URI you want to publish and let NeoLine handle the signing. AgentPay verifies Neo N3 TestNet, sends the registration transaction to the live AgentRegistry contract, and then reads back the resulting agent ID from the chain log.
const AGENT_REGISTRY_HASH = '0x9bae46744fbd512506e77fc7dcca6cd0f7cee495';
const TESTNET_MAGIC = 894710606;
function ensureHash160(value) {
return value.startsWith('0x') ? value : `0x${value}`;
}
function delay(ms) {
return new Promise((resolve) => window.setTimeout(resolve, ms));
}
function normalizeTxid(value) {
if (typeof value === 'string' && value) {
return value;
}
if (value && typeof value.txid === 'string' && value.txid) {
return value.txid;
}
if (value && typeof value.txId === 'string' && value.txId) {
return value.txId;
}
throw new Error('Wallet returned an unexpected transaction hash response.');
}
function getNeoLineProvider() {
return new Promise((resolve, reject) => {
let settled = false;
const readyHandler = (event) => {
const candidate = event.detail?.provider;
if (candidate?.name !== 'NeoLine') return;
settled = true;
window.removeEventListener('Neo.DapiProvider.ready', readyHandler);
resolve(candidate);
};
window.addEventListener('Neo.DapiProvider.ready', readyHandler);
window.dispatchEvent(new Event('Neo.DapiProvider.request'));
window.setTimeout(() => {
if (settled) return;
window.removeEventListener('Neo.DapiProvider.ready', readyHandler);
reject(new Error('NeoLine not found. Install or unlock the wallet first.'));
}, 2500);
});
}
async function getSigningAccount(provider) {
const accounts = await provider.getAccounts();
if (accounts.length === 1) {
return accounts[0];
}
const selectedAddress = await provider.pickAddress('Choose the wallet that should receive agent payouts.');
const selected = (await provider.getAccounts()).find((account) => account.address === selectedAddress);
if (!selected) {
throw new Error('Selected account is not available.');
}
return selected;
}
async function getNextAgentId(provider) {
const result = await provider.call({
hash: AGENT_REGISTRY_HASH,
operation: 'getNextAgentId',
});
return result.stack[0].value;
}
async function waitForRegisteredAgentId(provider, txid, fallbackAgentId) {
for (let attempt = 0; attempt < 10; attempt += 1) {
try {
const log = await provider.getApplicationLog(txid);
const notification = log.executions
.flatMap((execution) => execution.notifications || [])
.find((entry) => entry.contract === AGENT_REGISTRY_HASH && entry.eventname === 'AgentRegistered');
if (notification?.state?.type === 'Array') {
return notification.state.value[0].value;
}
} catch {
// The tx can take a few blocks to appear in the application log.
}
await delay(1500);
}
return fallbackAgentId;
}
async function registerWithNeoLine(metadataUri) {
if (!metadataUri || metadataUri.length > 512) {
throw new Error('metadataUri must be between 1 and 512 characters.');
}
const provider = await getNeoLineProvider();
if (provider.network !== TESTNET_MAGIC) {
throw new Error('Switch the wallet to Neo N3 TestNet first.');
}
const expectedAgentId = await getNextAgentId(provider);
const account = await getSigningAccount(provider);
const accountHash = ensureHash160(account.hash);
const txid = normalizeTxid(await provider.invoke(
[{
hash: AGENT_REGISTRY_HASH,
operation: 'registerAgent',
args: [
{ type: 'Hash160', value: accountHash },
{ type: 'String', value: metadataUri },
],
abortOnFail: true,
}],
[{
account: accountHash,
scopes: 'CalledByEntry',
}],
));
return { provider, expectedAgentId, address: account.address, txid };
}
async function confirmRegistration(provider, txid, expectedAgentId) {
const agentId = await waitForRegisteredAgentId(provider, txid, expectedAgentId);
return { agentId, txid };
}
// Example:
// const submission = await registerWithNeoLine('https://example.com/agent.json');
// const confirmation = await confirmRegistration(submission.provider, submission.txid, submission.expectedAgentId);
Pay another agent
Enter the target agent ID, the GAS amount, and optional payment metadata. NeoLine keeps a human in the loop. SponsorPay AutoPay spends only GAS that the selected wallet already deposited into SponsorVault and only inside that wallet's limits.
const GAS_HASH = '0xd2a4cff31913016155e38e474a2c06d08be276cf';
const PAYMENT_ROUTER_HASH = '0xd5289080662027bbc951622336acfd2c546f1180';
const AGENT_REGISTRY_HASH = '0x9bae46744fbd512506e77fc7dcca6cd0f7cee495';
// Reuse getNeoLineProvider(), getSigningAccount(), and ensureHash160() from the registration flow.
function base64ToHash160(value) {
const raw = atob(value);
const bytes = Array.from(raw, (char) => char.charCodeAt(0));
bytes.reverse();
return `0x${bytes.map((byte) => byte.toString(16).padStart(2, '0')).join('')}`;
}
function gasToDatoshiString(amount) {
const [whole, fraction = ''] = String(amount).trim().split('.');
const paddedFraction = `${fraction}00000000`.slice(0, 8);
return (BigInt(whole || '0') * 100000000n + BigInt(paddedFraction)).toString();
}
async function resolveAgentPaymentData(provider, agentId) {
const [activeResult, paymentAddressResult] = await Promise.all([
provider.call({
hash: AGENT_REGISTRY_HASH,
operation: 'isAgentActive',
args: [{ type: 'Integer', value: String(agentId) }],
}),
provider.call({
hash: AGENT_REGISTRY_HASH,
operation: 'getAgentPaymentAddress',
args: [{ type: 'Integer', value: String(agentId) }],
}),
]);
if (!activeResult.stack[0].value) {
throw new Error(`Agent ${agentId} is not active.`);
}
return {
paymentAddressHash: base64ToHash160(paymentAddressResult.stack[0].value),
};
}
async function payAgentWithNeoLine({ agentId, gasAmount, metadataUri }) {
const provider = await getNeoLineProvider();
if (provider.network !== TESTNET_MAGIC) {
throw new Error('Switch NeoLine to Neo N3 TestNet first.');
}
const payer = await getSigningAccount(provider);
const payerHash = ensureHash160(payer.hash);
const paymentData = await resolveAgentPaymentData(provider, agentId);
const txid = await provider.invoke(
[{
hash: GAS_HASH,
operation: 'transfer',
args: [
{ type: 'Hash160', value: payerHash },
{ type: 'Hash160', value: PAYMENT_ROUTER_HASH },
{ type: 'Integer', value: gasToDatoshiString(gasAmount) },
{
type: 'Array',
value: [
{ type: 'Integer', value: String(agentId) },
{ type: 'Hash160', value: paymentData.paymentAddressHash },
{ type: 'String', value: metadataUri || '' },
],
},
],
abortOnFail: true,
}],
[{
account: payerHash,
scopes: 'CalledByEntry',
}],
);
return {
agentId: String(agentId),
paymentAddressHash: paymentData.paymentAddressHash,
txid,
};
}
// Example:
// const submission = await payAgentWithNeoLine({ agentId: 4, gasAmount: '1.25', metadataUri: 'invoice://agentpay/order-42' });
// console.log(submission.txid);
Autonomous settlement
AutoPay routes through a C# AgentPay relayer and an on-chain SponsorVault. The vault debits only the payer's deposited balance, sends GAS into PaymentRouter, and records the payer while the relayer transaction fee is covered from protocol fee reserves.