Besides its NEAR Intents balance, every agent has its own keys outside NEAR Intents: a NEAR account, one EVM address for every EVM chain and a Solana address. With them the agent can prove its identity to another service, send NEAR, call a NEAR contract, sign EVM and Solana messages and transactions, log in with Solana, send one Solana transfer, and sign NEAR Intents payloads from its EVM or Solana key’s own Intents account. Every method is off until the owner switches it on in the policy’s native block. Each one has its own route under POST /v1/agents/{agent_id}/native/….
Native methods are for advanced users. The account’s policy does not protect them: budget, timelock_ms, schedule and owner_approval don’t apply, and destinations binds only the NEAR a transfer sends or a call attaches. A NEAR call can move anything the listed contract holds for the agent. A signed EVM or Solana transaction, or EVM typed data such as a permit, can move everything at that address. Switch on only what the agent needs, for services the owner trusts.The newer bounded methods have rules of their own: sign_intent, solana.sign_in and solana.sign_transfer each sign one narrow kind of thing and are the safer choice over sign_message and sign_transaction whenever they fit.

The agent’s addresses

GET /v1/agents/{agent_id}/wallet returns the agent’s addresses. The EVM and Solana keys are separate keys. Nothing they sign can reach the agent’s own NEAR Intents balance, the one held under near_account_id. The NEAR key can never sign for, call or send to the NEAR Intents contracts (intents.near, intents.far), so it can’t reach that balance either. The EVM and Solana keys do have a NEAR Intents account of their own, apart from the agent’s balance: the lowercase 0x EVM address, and the lowercase hex of the 32-byte key behind solana_account_id. Funds held there are what sign-intent spends. The EVM routes take a chain: ethereum, polygon, base, arbitrum, optimism, bsc, avalanche, hyperevm or hood. The address is the same on all of them.

Turn methods on

The owner adds native to the policy. Read the current policy, set native, and send the complete policy as a policy_update:
  • Omitted means every method is off. When native is present, every key in it is required except solana.sign_in, evm.sign_intent, solana.sign_intent and solana.sign_transfer, which may be left out (off). An empty list is off.
  • Lists hold up to 256 unique NEAR accounts (near.sign_message, near.call), up to 256 unique lowercase hosts, with their port if they have one (solana.sign_in), or up to 64 domain entries, unique by domain (evm.sign_typed_data).
  • intents.near and intents.far can never be listed for the NEAR key. A policy that lists them is refused with validation_failed. The EVM and Solana keys reach them only through sign-intent.
  • Transactions are checked before anything is signed. native/evm/sign-transaction takes one serialized unsigned EIP-2718 typed or EIP-155 legacy transaction whose chain id is that of chain. native/solana/sign-transaction takes one legacy or v0 transaction message. Anything else is refused with validation_failed, so neither route signs a message.
  • Every grant on the agent can use a method once it is on. Revoking a grant stops only that grant.
  • The wallet provider enforces the switches too, so changing native is an on-chain policy change. A NEAR owner signs it as nep366, the API pays the gas, and the policy cooldown applies. A method works once the revision is APPLIED and provider_policy_synced is true.
  • The owner’s preview.summary names every method the policy switches on and says which of the account’s money rules they skip.
Show the owner a warning before they sign a policy that switches on any native method, for example: “Native methods are for advanced users and are not protected by this account’s policy: budget, timelock, schedule and owner approval don’t apply. A NEAR transfer goes only to the NEAR accounts your destinations allow, but a NEAR call can move anything the listed contract holds for the agent, and a signed EVM or Solana transaction, or EVM typed data such as a permit, can move everything at that address. Only switch on what the agent needs, for services you trust; a service may treat an identity signature as you logging in.”

What applies

Every native request needs a live grant token (X-Grant-Token) and an applied policy, and is refused while the agent is frozen. The policy switch is the fence, with one money rule beside it: destinations binds the NEAR a transfer sends or a call attaches, and the recipient of a Solana sign-transfer. sign-intent has its own rules instead. Every native request is recorded in the agent’s history (GET /v1/agents/{agent_id}/history) as type native. Signatures are not stored in it. See History.

Sign a message (NEAR)

An agent can sign a NEP-413 message with its NEAR key, which is how a site or dApp logs in a NEAR account (signMessage). The service, the relying party, issues a message and a random 32-byte nonce and names itself as the recipient. Your backend has the API sign exactly those, and the relying party checks the signature. It moves no money. Turn it on by listing the relying party in native.near.sign_message. An entry is a NEAR account or a domain (printable ASCII, up to 128 characters), matched exactly, with no wildcard.
This route signs a NEP-413 message only. It never signs a transaction or an intent, and never for the NEAR Intents contracts (intents.near, intents.far), in any letter case.

Sign a message

Your backend receives message, recipient and nonce from the relying party and sends them unchanged with the grant token. The API key alone is refused with agent_grant_required.
callback_url is not supported. The response is 200. It answers at once, with nothing to poll. correlation_id names the history record.
The API verifies the signature against the NEP-413 payload before it returns it.

Verify a signature (relying party)

The relying party checks, in order:
  1. recipient is its own account or domain, and nonce is one it issued, unused.
  2. The signature (base64) verifies with public_key over the NEP-413 payload: the exact message it issued, the decoded 32-byte nonce, recipient, and no callback URL.
  3. public_key controls near_account_id: a full-access key on that account, read at final. For an implicit account that doesn’t exist on-chain yet, the account ID is the hex of that key.
Then it marks the nonce as used. The native accounts examples contain a complete login request builder and verifier in TypeScript. A signature already issued stays valid for the relying party until it expires the nonce, even after the owner removes the recipient.

Send NEAR and call contracts

native/near/transfer sends native NEAR from the agent’s NEAR account. native/near/call calls a contract listed in native.near.call. Both run outside NEAR Intents, and the agent’s NEAR account pays the gas. Its native NEAR must cover the attached deposit plus the gas the call prepays, else the call is refused with custody_native_balance_required, which states both amounts when the provider does. Don’t attach the whole balance.
Transfer fields: Call fields: A request can be at most 65,536 bytes as JSON.
Listing a token contract in native.near.call lets the agent send that token anywhere from its NEAR account.
NEAR that a transfer sends, or a call attaches as its deposit, must land where the policy’s destinations allows. The API judges it as a withdraw on chain near to the receiving account, and refuses it with policy_destination_denied (403) otherwise. budget, timelock_ms, schedule and owner_approval still don’t apply. A call can move anything the listed contract holds for the agent, whatever destinations says. Both routes need an Idempotency-Key and answer 202 with a status of type native. Track it with GET /v1/status like any execution. The provider’s word does not settle it. Once the provider names a transaction (details.tx_hash), the API reads the outcome from NEAR alone:
  • PROCESSING until two RPC operators agree that NEAR has finalized the transaction and that it is exactly the request: signed by the agent’s account, to the named recipient or contract, with the amount, method, arguments, gas (when given) and deposit asked for. details.provider_status does not change this.
  • SUCCESS once they agree, with details.tx_hash.
  • FAILED with native_transaction_failed when the finalized transaction failed. failure_reason has the chain’s reason. A transaction the provider reports as broadcast and reverted keeps its real details.tx_hash and provider_status onchain_tx_failed, and reads FAILED once the chain confirms it. Nothing it asked for happened, and it is never resent.
  • NEEDS_REVIEW when automatic settlement stopped: native_transaction_mismatch (the finalized transaction is not the request) or native_transaction_unconfirmed (NEAR still does not know it 30 minutes after dispatch), among others. See Idempotency and review.

Sign EVM typed data

native/evm/sign-typed-data signs EIP-712 v4 typed data, as eth_signTypedData_v4 takes it, with the agent’s EVM key. It signs only under a domain the owner listed in native.evm.sign_typed_data.
TypeScript SDK
A listed domain has the EIP-712 domain fields in the policy’s snake_case spelling: Each field is optional, but a domain names at least one. The rules:
  • Field for field. A request’s domain must equal one listed entry: the same fields present, with the same values. A missing or extra field makes it another domain, which is refused.
  • Spelling. chainId may be a number, a decimal string or a hex string. Addresses and salts compare lowercase.
  • types.EIP712Domain may be omitted, and the API derives it. When you send it, it must declare exactly the domain’s fields, in EIP-712 order (name, version, chainId, verifyingContract, salt), with their standard types.
  • primaryType is the message type. EIP712Domain is refused.
  • message is at most 65536 bytes as canonical JSON. A larger one is refused with validation_failed.
A domain with a field EIP-712 doesn’t define, a types.EIP712Domain that doesn’t match, or primaryType: "EIP712Domain" is refused with validation_failed. A well-formed domain that isn’t listed, or a primaryType the matching entry doesn’t allow, is refused with native_method_denied. A protocol separates powers by domain. Hyperliquid trades under its Exchange domain and withdraws under HyperliquidSignTransaction. Listing Exchange lets the agent trade, but it never signs a HyperliquidSignTransaction withdrawal.
Typed data can authorize transfers (EIP-3009 TransferWithAuthorization) and approvals (EIP-2612 Permit) of anything the EVM address holds. List only the domains of services the owner trusts.

Limit the message types

A protocol often signs several powers under one domain. Hyperliquid’s HyperliquidSignTransaction domain signs HyperliquidTransaction:Withdraw, and also HyperliquidTransaction:SpotSend and HyperliquidTransaction:UsdSend, which pay any address. An entry can list the message types it allows in primary_types:
  • primary_types is optional: 1 to 32 unique strings, each 1 to 256 characters, case-sensitive, never EIP712Domain. An empty list is invalid. Omitted means any message type under the domain.
  • A request matches when its domain equals the entry’s domain field for field, and either the entry has no primary_types or the request’s primaryType is listed. Otherwise it is refused with native_method_denied.
  • One entry per domain. Entries are unique by domain, so one domain listed twice with different types is refused. Give one entry all its types.
primary_types narrows the kind of message, never its fields. An allowed HyperliquidTransaction:Withdraw can still name any destination and amount.

Sign EVM messages and transactions

evm.sign_transaction gives full control of the EVM address. Nothing is broadcast: you send the signed transaction yourself. native/evm/sign-message refuses bytes that are a NEAR Intents payload, in any encoding, with intents_payload_use_sign_intent (403). Use sign-intent for those. A deployment can turn this refusal off for both message routes. Where it is off, evm.sign_message amounts to full control of the key’s NEAR Intents account.

Sign Solana messages and transactions

solana.sign_transaction gives full control of the Solana address. Nothing is broadcast: you send the signed transaction yourself. native/solana/sign-message also refuses bytes that are a NEAR Intents payload, with intents_payload_use_sign_intent (403). It signs any other bytes as sent, 32-byte digests included, and NEAR Intents accepts such a digest under other signature standards. solana.sign_message therefore amounts to full control of that key’s NEAR Intents account, the lowercase hex of its public key, with none of the checks sign-intent makes. Give it only to an agent the owner trusts with that account. For a login use solana.sign_in, and for a payment solana.sign_transfer or sign-intent.

Sign NEAR Intents payloads (EVM and Solana)

native/evm/sign-intent and native/solana/sign-intent let the agent’s EVM or Solana key authorize a NEAR Intents transfer from that key’s own Intents account. The answer is ready to publish to intents.near (execute_intents), to 1Click or to a solver relay. The EVM and Solana keys sign for the NEAR Intents contracts only this way. Each key has its own rules, evm.sign_intent and solana.sign_intent, apart from sign_message. Neither opens the other’s route. The rules are the method’s own: the policy’s destinations, assets, limits, budget, timelock_ms, schedule and owner_approval never judge what it signs.
A freeze stops new payloads, not signed ones. A payload that was signed before the owner froze the agent stays publishable until its own deadline, by anyone who holds it. For a payload that pays only 1Click deposits that is up to 96 hours. Freezing is not an instant stop for this method. Keep the signed payload on your backend, publish it at once, and sign a payload only when you mean to publish it.

The sign-intent rules

Omitting sign_intent turns the method off. Present means on.
receivers is the only rule that fits a plain transfer: a listed receiver is paid whatever the payload’s tokens say, from the Intents balance of that key’s account, and anyone who holds the payload can publish it until its deadline. There is no amount limit. A 1Click deposit is held to its quote instead: it is paid with exactly the quote’s amount, and the loss at the quote’s minimum output, fees included, is bounded by max_slippage_bps.

What the payload must be

The body is { payload, one_click_quotes? }. payload is the exact JSON text of { signer_id, verifying_contract, deadline, nonce, intents }, as JSON.stringify of that object writes it, with no whitespace and no repeated or unknown keys. The route signs the text it was given, so nothing can re-serialize it between your backend and the signature. one_click_quotes is for 1Click deposits (below).
  • Intents. intents holds only transfer intents (intent, receiver_id, tokens, and optionally memo, msg and min_gas), or none. Anything else, such as add_public_key, token_diff or a withdrawal, is refused with intents_type_forbidden, and source.pointer names the intent.
  • Signer. signer_id is the signing key’s Intents account: the lowercase 0x EVM address (wallet.evm_account_id, lowercased; a checksummed spelling is refused), or the lowercase hex of the 32-byte key behind wallet.solana_account_id. Otherwise intents_signer_mismatch.
  • Contract. verifying_contract is intents.near or intents.far, and no receiver may be either of them (intents_contract_forbidden).
  • Deadline. An RFC 3339 UTC time ending in Z, still ahead and at most 300 seconds after the API’s clock. A payload that pays only 1Click deposits (below) may be due up to 96 hours ahead, which is the deadline 1Click writes into the payloads it generates. Otherwise intents_deadline_invalid. Anyone who holds a signed payload can publish it until then, so sign one that is due soon.
  • Nonce. Base64 of 32 bytes.
  • Receivers. Every receiver_id must be a listed receiver, or one of at most 2 1Click deposit addresses per payload whose signed quote in one_click_quotes passes the checks below. Otherwise policy_destination_denied (403), with the reason in detail, before the wallet provider is called.
Any other malformed field is intents_payload_invalid (400), with the field in source.pointer.

1Click deposits

A receiver that is not listed must be a 1Click deposit address, and the request must carry the quote 1Click gave for it. Put up to 2 quotes in one_click_quotes, one per deposit address the payload pays outside sign_intent.receivers. Each is the answer of 1Click’s POST /v0/quote, sent unchanged (quote, quoteRequest, signature, timestamp and the rest). Only live quotes count: dry must be false. The API checks 1Click’s ed25519 signature on each quote offline, against 1Click’s quote key ed25519:reYaWhvwu8Jzo3WUM3zhn6VrhuMEF4eADL17qtRVifc (the key the 1Click SDK’s verifyQuoteSignature uses). It does not call 1Click to read a deposit’s status. Confidential deposits (intents.far) work this way, and public ones (intents.near) need the quote too. It signs only when the quote:
  • carries 1Click’s valid signature, and is live. A deposit with no quote, or a quote that is unsigned, altered or a dry run, is refused with policy_destination_denied. A quote for an address the payload does not pay, or two quotes for one address, is validation_failed (/one_click_quotes/{i}/quote/depositAddress);
  • pays out where the owner allowed. With one_click_deposits: to the payload’s signer_id or to a listed receiver, inside NEAR Intents (recipientType is the contract’s account type). With a withdrawals entry: to a listed address on the chain 1Click lists the quote’s destinationAsset on (recipientType DESTINATION_CHAIN). The chain comes from 1Click’s token listing, never from the quote request. If the listing cannot be read, the request is refused with provider_unavailable;
  • refunds the signing key (refundTo is the signer_id), with refundType and depositType equal to the contract’s account type: INTENTS for intents.near, CONFIDENTIAL_INTENTS for intents.far;
  • has no rebates, customRecipientMsg, virtualChainRecipient or virtualChainRefundRecipient;
  • loses no more at minAmountOut than max_slippage_bps allows, by 1Click’s signed USD amounts. 1Click’s fee and slippage both count, and appFees and slippageTolerance are not read. Request slippage clearly below the cap, such as 50 under a cap of 100: a quote at exactly the cap is refused, because fees and spread push it over;
  • is paid by one transfer of exactly the quote’s amountIn of its originAsset, due no later than the quote’s deadline. On intents.far the token is the confidential imt:51e8f94d77b5e90dc9852ca6113771e11e8382ce69473a75e313e41665de7cbe:<originAsset> token, which is how 1Click generates it.
Sign-intent takes no Idempotency-Key. A retry is judged again, and a refused payload never reaches the wallet provider. A deposit that 1Click has closed since the quote is not detected, and a payment that arrives late refunds to the signer. A listed receiver as the quote’s recipient lets the key move confidential funds to another of the owner’s accounts, such as the agent’s NEAR account, to use /withdraw, where a plain transfer on intents.far has no public RPC to be published through.

Use it end to end

Your backend drives the whole flow with its own 1Click access. The Agent API only signs.
1

Switch it on

The owner adds sign_intent to native.evm or native.solana with a policy_update and waits until provider_policy_synced is true. List the owner’s own accounts and addresses only.
2

Get a 1Click quote

Request a quote with POST /v0/quote and dry: false. Use depositType INTENTS (or CONFIDENTIAL_INTENTS for intents.far), and set refundTo to the key’s Intents account (signer_id) with the same refundType. Set the recipient to the key’s own account, a listed receiver or a listed withdrawals address. Request slippage clearly below max_slippage_bps (50 under a cap of 100), and send no rebates or custom messages. Keep the whole answer, not only the depositAddress. See Signed Intent Execution.
3

Generate the payload

Call 1Click’s POST /v0/generate-intent with the depositAddress, signerId set to the key’s Intents account, and the standard the key signs: erc191 for the EVM key, raw_ed25519 for the Solana key. Its intent.payload is the text to sign.
4

Sign it with the agent's key

Send intent.payload unchanged to the Agent API, with the grant token and the quote from step 2 in one_click_quotes, also unchanged.
5

Publish the signed data

Publish signed_data as is, to 1Click with POST /v0/submit-intent (type: "swap_transfer", signedData: signed_data), to intents.near with execute_intents, or to a solver relay. The Agent API does not publish anything and records no outcome. Follow the swap in 1Click’s status for the depositAddress.
For a plain transfer to a listed receiver, build the payload yourself. Order the keys as shown and send exactly this text:

The answer

The usual signature plus signed_data. For the EVM key, chain is ethereum, because personal_sign ignores the chain.
The first is the EVM key’s, with signature as r‖s‖v and v as 0 or 1. The second is the Solana key’s. Before it answers, the API checks the signature against the agent’s own key and the payload’s bytes: the address recovered from the ERC-191 signature must be evm_account_id, and the Ed25519 signature must verify under the Solana key. A signature that does not is discarded and the request is refused with provider_signature_invalid (502). The history record keeps the payload’s hash and the contract as target, not the payload.

Sign in with Solana

native/solana/sign-in is Sign-In-With-Solana with the agent’s Solana key, so a dApp or service can log the agent in as solana_account_id without solana.sign_message. Turn it on by listing the service’s domain in native.solana.sign_in: a lowercase host, with its port if it has one, matched exactly. The API writes the message itself, as the wallet standard’s createSignInMessageText does, with the agent’s address. The signature is therefore a login for that domain and nothing else: the text is never JSON, so it is no NEAR Intents payload, and it is longer than 32 bytes, so it is no digest another signature standard signs.
TypeScript SDK
not_before, request_id and resources are not taken. A field that breaks these rules is refused with validation_failed, with the field in source.pointer. A domain that is not listed is native_method_denied. The answer is the usual signature plus signed_message, the exact text that was signed. Give the relying party its UTF-8 bytes with signature and account_id. The API checks the signature over that text before it releases it. The history record keeps the domain as target and the request’s hash, not the message.

Send a Solana transfer

native/solana/sign-transfer signs one SOL or token transfer from the agent’s Solana key without solana.sign_transaction, which signs any transaction and gives full control of the address. It needs native.solana.sign_transfer: true. Nothing is broadcast: you send the signed transaction yourself, while its blockhash is still recent.
TypeScript SDK
The API reads every instruction of the message and judges recipient against destinations as a withdraw on chain sol, with no memo. It refuses with policy_destination_denied (403) before it calls the wallet provider, and signs the exact bytes it read. The message must be:
  • a legacy or version 0 message with no address lookup tables, so every account is in it;
  • signed by the agent only, which is also the fee payer, with no account appearing twice;
  • exactly one transfer: a System Program Transfer from the agent to recipient, or one SPL Token or Token-2022 TransferChecked with the agent as authority into the associated token account of recipient for that mint.
Besides the transfer it may hold one associated-token-account CreateIdempotent of that account, paid by the agent, and one compute unit limit and one compute unit price whose priority fee is at most 1,000,000 lamports. Anything else, from a memo to a durable-nonce AdvanceNonceAccount, is refused with solana_transaction_forbidden (403), with the reason in detail, and nothing is signed.
Amounts, assets, limits and budget are not checked yet. sign_transfer stops transfers to arbitrary recipients, but does not limit how much is sent to an allowed one.
The history record keeps the recipient as target, the amount (lamports, or the token’s atomic units) and the transfer as read in message_json: recipient, mint, token_program and amount.

The signature

The EVM and Solana routes answer 200 with the signature. They take no Idempotency-Key and have nothing to poll. The signing call is the only place a signature is returned. Status and history return signature: null, for older records too.

Idempotency and review

A NEAR transfer or call takes an Idempotency-Key like any execution. The same key with the same body returns the same operation and never sends twice. See Idempotency. If the provider’s answer is lost, the API sends the original request again under the same provider key, which returns the original request. It does this for 10 minutes after dispatch. After that the request reads NEEDS_REVIEW with failure_code native_request_unconfirmed, and it is never resent. Nothing can resume it: a native transfer or call has no recover. NEEDS_REVIEW can also carry native_transaction_mismatch (NEAR finalized a transaction that is not the request), native_transaction_unconfirmed (NEAR still does not know the transaction 30 minutes after dispatch), provider_review_required, or provider_partially_failed when part of a call ran. In every case:
  • Stop polling, and never send it again under a new key. It may have run.
  • After the review is resolved, read the status with refresh=true. See NEEDS_REVIEW.
The signing routes take no Idempotency-Key. Each request signs again and records its own history entry.

History

Every native request is one history entry of type native. Its details say what was asked of the key:

Turn it off

Remove an entry from a list, set a switch to false, or omit native to turn every method off. Each is an on-chain policy change, like turning it on. Freezing the agent stops every native route at once (wallet_frozen), but it cannot recall a signature already returned. A sign-intent payload signed before the freeze stays publishable until its own deadline, up to 96 hours for one that pays only 1Click deposits. Revoking a grant stops only that grant, and recalls nothing either. Deleting the agent doesn’t sweep funds at its EVM or Solana address to the owner, and native NEAR left in its NEAR account goes to the sponsor. Move them first. See Deleting an agent.

Errors

signing_recipient_forbidden is checked first, before the grant and the policy. See Status & Errors for the error format and every other code.