External farming
Hand us wallets you already hold and we give them a life. Daily on-chain activity under a persona, for as long as you ask, with the keys returned or forgotten at the end.
Everything else on this site is about wallets the platform produced. External farming is the other direction: wallets you already hold, whose keys you hand over, and which the platform then trades on your behalf so they build the same kind of history the catalog sells. A wallet you bought here can be enrolled too, to keep its activity going after the sale.
Nothing is funded for you here. The wallets farm with what they hold, or with the slice of it you let them have: see How much it trades with.
What is on offer
GET /aging/external/presets lists what an enrolment can ask for: the chains
farming runs on, the personas available with the chains each is calibrated for,
the timezone profiles (an IANA zone and a waking window), the tiers, and the
limits on a request.
Authorization
bearerAuth JWT from /auth/login (users) or an org API key (mk_...) for integrations.
In: header
Response Body
application/json
curl -X GET "https://example.com/aging/external/presets"{ "chains": [ "string" ], "limits": { "max_duration_days": 0, "max_tx_per_day": 0, "max_wallets_per_request": 0 }, "personas": [ { "chains": [ "string" ], "description": "string", "id": "string", "name": "string" } ], "tiers": [ "string" ], "timezone_profiles": [ { "iana": "string", "id": "string", "name": "string", "wake_end": "string", "wake_start": "string" } ]}Enrolling
POST /aging/external/enroll takes a batch of private keys and a farming
profile shared by the batch.
{
"wallets": [
{ "secret_key": "0x…", "buyer_ref": "customer-42" },
{ "secret_key": "5Kd…", "chains": ["solana"], "label": "treasury-2" }
],
"chains": ["ethereum", "base"],
"persona": "memecoin_degen",
"timezone_profile": "europe_day",
"tier": "tier2",
"tx_per_day_min": 2,
"tx_per_day_max": 6,
"duration_days": 30,
"budget_min_native": "500000000000000000",
"budget_max_native": "1000000000000000000",
"budget_compound": true,
"token_mints": ["0x…", "0x…"],
"token_inherit_default": true
}A key is accepted as hex for an EVM wallet, with or without the 0x, and as
base58, hex or a JSON byte array for a Solana keypair. It is encrypted on
arrival like every other key the platform holds, and never written down in
clear. See Security.
Each key farms only the chains of its own network. A Solana key farms
solana; an EVM key farms every EVM chain you list, since it is the same
address on all of them, and the first one listed is its home chain. A
per-wallet chains overrides the request's list for that wallet.
persona and timezone_profile take an id or a name from the presets. tier
defaults to tier1 and only shapes how positions are handled. Every wallet
draws its own daily rate from [tx_per_day_min, tx_per_day_max], so a batch
does not move in lockstep, and the farm runs duration_days from now.
The last five fields bound what the farm may do with each wallet. They are optional, they apply to every wallet of the request, and each wallet may carry its own copy to override them. They have their own section below.
The response names what was enrolled and what was skipped, with a reason per
skipped wallet, and for each enrolled wallet the daily rate (tx_per_day) and
the budget (budget_native) that were drawn for it. An address you had enrolled
before is re-enrolled on the new plan, flagged reenrolled. A wallet bought on
the marketplace is flagged purchased: it stays in your purchases and farms on
top.
With a user session this call needs an organisation admin. With an API key it does not: a backend integration is expected to drive enrolment, and an API key is already the organisation's credential.
Authorization
bearerAuth JWT from /auth/login (users) or an org API key (mk_...) for integrations.
In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Per-wallet farming limits: how much native to trade with, and which tokens.
These fields appear both on the request (as the default for every wallet in it) and on each wallet (overriding that default). The budget range and the token list each move as a unit: a wallet naming one bound, or one mint, replaces that whole setting rather than half of it.
Response Body
application/json
curl -X POST "https://example.com/aging/external/enroll" \ -H "Content-Type: application/json" \ -d '{ "chains": [ "string" ], "duration_days": 0, "persona": "string", "timezone_profile": "string", "tx_per_day_max": 0, "tx_per_day_min": 0, "wallets": [ { "secret_key": "string" } ] }'{ "enrolled": [ { "budget_native": "string", "buyer_ref": "string", "chains": [ "string" ], "ends_at": "string", "pubkey": "string", "purchased": true, "reenrolled": true, "tx_per_day": 0, "wallet_id": "string" } ], "skipped": [ { "reason": "string", "reference": "string" } ]}How much it trades with
Left to itself, a farm trades with the wallet's whole balance: every buy is sized as a share of what the wallet holds, as the persona dictates. A customer handing over a wallet with 10 SOL on it has all 10 SOL in play. The budget fields cap that.
The budget
budget_min_native and budget_max_native give, in base units of the home
chain, the range the wallet's budget is drawn from. Each wallet draws its own
figure, the way it draws its own daily rate, so a batch enrolled on one range
does not trade in lockstep. Name one bound only and the budget is pinned to it
exactly. Name neither and there is no cap, which is how every farm behaved
before these fields existed.
The budget is checked against the wallet itself, at enrolment:
- the maximum may not exceed what the wallet actually holds, since the budget is a slice of the balance and not a promise to fund it;
- the minimum has to be large enough to fund one swap on the wallet's chains, or the farm would schedule buys that can only ever fail.
Either refusal skips that one wallet with a reason, and the rest of the request goes through.
It revolves
The budget is an envelope, not a spend cap. Nothing counts how much the farm has spent so far. The one invariant is that the native balance never falls below what the wallet held at enrolment minus the budget: a 10 SOL wallet on a 1 SOL budget never goes under 9 SOL. Buying moves money out of the envelope into a position, selling moves it back, and the farm keeps cycling on the same slice for as long as it runs.
Mid-position the envelope reads smaller, because the native is deployed, so the next buy scales down to what is actually free. It never reads larger than the truth, and the floor holds either way.
Compounding
budget_compound, on by default, lets realised gains grow the envelope. A 1
SOL budget whose positions sell back for 1.5 SOL then trades with 1.5 SOL.
Switch it off and the envelope stays at its nominal size: the gains sit on the
wallet, untouched by farming, above the floor.
Losses need no setting. A position that sells for less than it cost leaves the envelope smaller, and the floor is what stops a losing streak from eating into the rest of the wallet.
Which tokens
token_mints pins the wallet to a list of tokens. By default
(token_inherit_default: true) they are added to the persona's own universe
for each chain; set it to false and the list is the universe, and the wallet
trades nothing else. Every pinned mint must be known and tradeable on at least
one of the wallet's chains, or the wallet is skipped with the offending mints
named. A replacing list that resolves to nothing on one of the wallet's chains
is refused too, since it would leave that chain with nothing to trade.
Unlike the budget, the token list is not checked against the wallet's holdings: it says what the farm may buy, not what the wallet has.
Both settings move as a unit. A wallet naming one budget bound, or one mint, replaces the request's whole budget range, or whole token list, rather than half of it.
Following the farm
GET /aging/external lists your farmed wallets, running ones by default and
stopped ones too with include_stopped=true, filterable by buyer_ref. Each
row carries the plan (chains, the rate actually drawn as tx_per_day,
duration_days, started_at, ends_at), where it stands (state is
running, stopped or finished, stop_reason says why when it stopped), and
what the wallet looks like now: balance on its home chain, tx_count,
persona, timezone, tier. The limits it was enrolled with are there too:
the budget drawn (budget_native, null when uncapped) with the range it came
from, budget_compound, and the pinned token_mints with
token_inherit_default.
A farm ends on its own at ends_at. The keys are kept; stop with forget if
you want them gone.
Authorization
bearerAuth JWT from /auth/login (users) or an org API key (mk_...) for integrations.
In: header
Query Parameters
Only the wallets tagged with this buyer reference.
nullfalseint6450int640Response Body
application/json
curl -X GET "https://example.com/aging/external"[ { "aging_status": "string", "balance": "string", "budget_compound": true, "budget_max_native": "string", "budget_min_native": "string", "budget_native": "string", "buyer_ref": "string", "chains": [ "string" ], "duration_days": 0, "ends_at": "string", "label": "string", "network": "string", "next_op_at": "string", "ops_completed": 0, "ops_failed": 0, "ops_pending": 0, "ops_skipped": 0, "org_id": "string", "org_name": "string", "persona": "string", "pubkey": "string", "purchased": true, "start_balance": "string", "started_at": "string", "state": "string", "stop_reason": "string", "stopped_at": "string", "tier": "string", "timezone": "string", "token_inherit_default": true, "token_mints": [ "string" ], "tx_count": 0, "tx_per_day": 0, "tx_per_day_max": 0, "tx_per_day_min": 0, "wallet_id": "string", "wallet_status": "string" }]Stopping
POST /aging/external/stop takes a list of addresses and stops their farms.
With "forget": true the platform also drops the wallet and replaces its key by
a tombstone, the same way key retention
does. The same key can be enrolled again later; it just has to be sent again.
Addresses that are not yours, or not farmed, come back under skipped with a
reason, and the rest is stopped.
Authorization
bearerAuth JWT from /auth/login (users) or an org API key (mk_...) for integrations.
In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
curl -X POST "https://example.com/aging/external/stop" \ -H "Content-Type: application/json" \ -d '{ "pubkeys": [ "string" ] }'{ "skipped": [ { "reason": "string", "reference": "string" } ], "stopped": [ "string" ]}Last updated on