Skip to main content

Buy & Sell

One request, unsigned transactions back, signed and sent by your own script. The snippet below is complete: set the key, the token and the amount, and run it.

Argus
LIFT

One endpoint for every Argus and LIFT token on Arc, wherever it lives:

  • Argus tokens, in their Uniswap v4 pool with the Argus fee hook.
  • LIFT tokens, in their Uniswap v4 pool with the LIFT hook. LIFT's earlier v1 launches sit in plain Uniswap v3 pools and report as UNISWAP.
  • Any other Uniswap pool on Arc priced in USDC.

You send the token address; the API finds the pool and routes accordingly, reporting protocol as ARGUS, LIFT or UNISWAP and venue as uniswap_v4 or uniswap_v3. There is no bonding curve and no graduation on Arc: a token trades in the same pool from its first block on.

What you need

  • Node.js or Python.
  • A wallet private key with some USDC on Arc. USDC is the chain's native token: it pays for the trade and for gas.
  • A token address. Take one from argus.world or lift.fun.

Example

const { Wallet } = require("ethers");

// ─── change these three ───────────────────────────────
const PRIVATE_KEY = "0xYOUR_PRIVATE_KEY";
const TOKEN = "0xTHE_TOKEN_ADDRESS";
const AMOUNT = "5"; // to buy: USDC to spend. To sell: "1000" tokens, or "100%"
const ACTION = "buy"; // "buy" or "sell"
const SLIPPAGE = 5; // %, how far the price may move against you
// ──────────────────────────────────────────────────────

async function main() {
const wallet = new Wallet(PRIVATE_KEY); // signs only; no node needed

const res = await fetch("https://api.shrine.trade/arc/api/local-trade", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
action: ACTION,
token: TOKEN,
amount: AMOUNT,
slippage: SLIPPAGE,
from: wallet.address,
}),
});

const data = await res.json();
if (data.error) throw new Error(`${data.error}: ${data.message}`);
console.log("you should get about", data.quote.expectedOutFormatted,
ACTION === "buy" ? "tokens" : "USDC");

// Sign locally and hand the signed bytes to the API to broadcast - the key
// never leaves this script, and you need no RPC of your own. The first
// trade of a token carries one-time approvals in front of the swap.
for (const tx of data.txs) {
const signed = await wallet.signTransaction({
to: tx.to, data: tx.data, value: BigInt(tx.value), gasLimit: BigInt(tx.gas),
maxFeePerGas: BigInt(tx.maxFeePerGas), maxPriorityFeePerGas: BigInt(tx.maxPriorityFeePerGas),
nonce: tx.nonce, chainId: tx.chainId, type: 2,
});
const sent = await (await fetch("https://api.shrine.trade/arc/api/send", {
method: "POST", headers: { "content-type": "application/json" },
body: JSON.stringify({ signedTx: signed }),
})).json();
if (sent.error) throw new Error(`${sent.error}: ${sent.message}`);
if (sent.status !== "landed") throw new Error(`${tx.description} ${sent.status}: ${sent.explorer}`);
console.log(tx.description + ": " + sent.explorer);
}

if (ACTION === "buy") {
console.log(`bought ${data.name} (${data.symbol}) ${data.token} for ${data.amountInFormatted} USDC on ${data.protocol}`);
} else {
console.log(`sold ${data.amountInFormatted} ${data.name} (${data.symbol}) ${data.token} for ${data.quote.expectedOutFormatted} USDC`);
}
}

main();

Save it as trade.js, then:

npm install ethers
node trade.js

Whatever the API returns, the script sends in sequence. After the first trade, a buy or a sell is a single transaction.

Trades are front-run protected: the on-chain minOut guarantees the fill is never worse than your slippage allows, and the signed transaction is broadcast through our own endpoint via /api/send, not a public node, so you need no RPC of your own.

One-time approvals

Every pool on Arc is priced in USDC, and the swap pulls your USDC through Permit2, Uniswap's shared approval contract. The first time a wallet buys, two approvals come back ahead of the swap: approve_quote_permit2 (USDC to Permit2, once ever) and approve_quote_router (Permit2 to the router, once a year). Every later buy is one transaction. The first sell of a token needs the same pair for that token, approve_permit2 and approve_router. The scripts above handle all of it.

Snipe tax on fresh launches

Both launchpads charge a steep extra fee on buys in the first moments after a launch, up to 99%, decaying to zero within seconds on Argus and within 6 blocks on LIFT. It is inside the quote, so a buy during that window shows a much smaller expectedOut. snipeTaxBps in the response is the rate that applied; Token Info shows it before you trade, so a bot can wait for 0.

Selling

Switch ACTION to "sell". For the amount, give either a token count or a percentage of the wallet's balance:

AMOUNTSells
"1000"1000 tokens
"50%"half your balance
"100%"everything

Proceeds arrive in USDC.

Creator tax

Most launches carry a creator tax: the launchpad's hook pays a percentage of every buy and sell to the token's creator, fixed at launch. The tax is applied inside the quote, so expectedOut is what you actually receive. creatorTaxBps in the response is the rate on your side of the trade. Nothing changes in the script.

If it doesn't work

ErrorWhat to do
insufficient_fundsAdd USDC to the wallet, on Arc: the amount plus a little for gas.
insufficient_balanceThe sell is bigger than the balance. "100%" always fits.
token_not_foundNot an Argus or LIFT token, and no Uniswap pool holds it against USDC. Make sure it is the token contract, not the pool.
slippage_exceededThe price ran away between quote and send. Retry, with a higher SLIPPAGE on a busy token.
amount_too_smallThe swap would return nothing. Increase AMOUNT.

Full list on Errors.

Sending

POST https://api.shrine.trade/arc/api/send takes one signed transaction and broadcasts it through our Arc endpoint, so the scripts need no RPC at all. It only forwards transactions this API built, swaps and their approvals, and refuses anything else with not_ours.

Field
signedTx0x-hexThe signed transaction, as wallet.signTransaction returns it.
waitboolean, optionalDefault true: wait for the receipt. false returns the hash as soon as the node accepts it.
{ "hash": "0x…", "status": "landed", "blockNumber": 22171031, "gasUsed": 214058, "effectiveGasPrice": "20300000000", "explorer": "https://explorer.arc.io/tx/0x…" }

status is landed, reverted, or pending (not waited for, or not mined within 45 seconds, stuck on nonce or gas).

Request and response

POST https://api.shrine.trade/arc/api/local-trade

Request

Field
action"buy" or "sell"
tokenaddressThe token.
amountdecimal stringOn a buy, the USDC to spend, such as "5". On a sell, a token count such as "1000", or a percentage of the balance such as "50%" or "100%".
fromaddressYour wallet.
slippagenumber, optionalIn percent. 5 when omitted.

Response, a real one: a buy of an Argus token for 5 USDC from a wallet that has never traded, so the two one-time approvals ride in front of the swap:

{
"action": "buy",
"protocol": "ARGUS",
"token": "0xb242508c29959Aa165379BCfdB576070b24ee5a8",
"name": "Xeusthegreat",
"symbol": "XEUS",
"venue": "uniswap_v4",
"pool": "0x6299828f0bbf42c06d571279bf83d06d930bb5bc4a99b98793c6ff47141030b1",
"amountIn": "5000000",
"amountInFormatted": "5",
"quoteAsset": {
"address": "0x3600000000000000000000000000000000000000",
"symbol": "USDC",
"decimals": 6,
"isNative": false
},
"quote": {
"expectedOut": "1917103258230312676855547",
"expectedOutFormatted": "1917103.258230312676855547",
"minOut": "1821248095318797043012770",
"poolFeeBps": 100,
"creatorTaxBps": 300,
"snipeTaxBps": 0,
"shrineFeeBps": 75,
"slippagePct": 5.0
},
"txs": [
{
"to": "0x3600000000000000000000000000000000000000",
"data": "0x095ea7b3…",
"value": "0",
"gas": 80000,
"maxFeePerGas": "27842282458",
"maxPriorityFeePerGas": "2842282458",
"nonce": 50,
"chainId": 5042,
"type": 2,
"description": "approve_quote_permit2"
},
{
"to": "0x000000000022D473030F116dDEE9F6B43aC78BA3",
"data": "0x87517c45…",
"value": "0",
"gas": 80000,
"maxFeePerGas": "27842282458",
"maxPriorityFeePerGas": "2842282458",
"nonce": 51,
"chainId": 5042,
"type": 2,
"description": "approve_quote_router"
},
{
"to": "0x4fcA4a51Ab4F23A7447b3284fBd7D73289A89Fb1",
"data": "0x3593564c…",
"value": "0",
"gas": 600000,
"maxFeePerGas": "27842282458",
"maxPriorityFeePerGas": "2842282458",
"nonce": 52,
"chainId": 5042,
"type": 2,
"description": "buy"
}
]
}
  • txs: the transactions to sign and broadcast, in this order. A swap that follows an approval in the same batch carries a fixed gas limit, since it cannot be estimated before the allowance exists; a swap on its own carries a real estimate plus 20%. The three wei-denominated fields (value, maxFeePerGas, maxPriorityFeePerGas) are decimal strings so they survive JSON; everything else is a plain number. description labels each: buy, sell, or the one-time approvals approve_quote_permit2, approve_quote_router, approve_permit2, approve_router.
  • name / symbol: read from the token contract, for logging what was traded.
  • protocol / venue / pool: which launchpad the token came from, and where it trades: uniswap_v4 with the pool id, or uniswap_v3 with the pool address.
  • quoteAsset: always USDC on Arc, 6 decimals. amountIn is in base units of it on a buy (5 USDC = 5000000), tokens on a sell.
  • quote.expectedOut: the amount you should end up with once the pool fee, the creator tax, any snipe tax and our fee are all taken. minOut is that figure less your slippage, and it is the floor the router enforces. Both are base units; use the *Formatted twins for display.
  • quote fees: poolFeeBps is the pool's own fee, creatorTaxBps the token's tax on this side, snipeTaxBps the early-buy surcharge that applied (0 outside the launch window, null for plain Uniswap pools), shrineFeeBps ours (75 = 0.75%).