---
name: launch-token-on-biddin
description: Launch a memecoin on biddin (Arc mainnet, chain 5042) from a chat instruction — one transaction that creates the token, opens its bonding curve, optionally buys the creator's own allocation and optionally enrols buyback-and-burn. Use when the user asks to launch, create or deploy a token on biddin or on Arc.
---

# Launch a token on biddin

biddin is a memecoin launchpad on Arc, Circle's L1 where gas is paid in USDC. One call to
`launchWithNative` creates the token, opens its bonding curve, and settles the creator's own buy —
with no approval step, because Arc's native balance is the same money as the USDC ERC-20.

Full reference: <https://biddin.win/docs.md>

## This skill spends real money

Launching costs **1.00 USDC plus gas**, and a dev buy spends whatever the user asks for. There is no
testnet fallback and no undo: a coin launched on mainnet exists forever and the fee is not
refundable.

**Before sending anything, show the user a summary and wait for an explicit yes.** Print exactly
these lines and stop:

```
About to launch on biddin (Arc mainnet, chain 5042):
  Name / symbol     <name> / <SYMBOL>
  Image             <imageUri>
  Creator tax       <x>%        (0 unless the user asked for one; cap read from the chain)
  Fees go to        <creatorFeeRecipient>
  Your own buy      <n> USDC    (0 if none)
  Buyback & burn    <on|off>
  Snipe exemptions  <count> wallet(s)
  Launch fee        <launchFee> USDC      (read in step 2, never hardcoded)
  Total to send     <launchFee + devBuy> USDC
  From wallet       <address>   balance <bal> USDC on chain <chainId>
Confirm to proceed.
```

Do not proceed on a vague reply. "Looks good" is a yes; silence, a follow-up question, or an edit is
not.

## Never ask for a private key in chat

Read it from `process.env.PRIVATE_KEY`. If it is not set, tell the user to set it in their shell and
stop — do not offer to accept it in the conversation, and never write it to a file.

## Steps

### 1. Check the chain and the wallet

```js
// npm i viem
import { createPublicClient, createWalletClient, http, defineChain, parseAbi, formatUnits } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';

const arc = defineChain({
  id: 5042,
  name: 'Arc',
  nativeCurrency: { name: 'USDC', symbol: 'USDC', decimals: 18 },
  // The free public node is fine for a one-off launch, but we have measured it refusing
  // every eth_call for minutes at a time while eth_blockNumber kept answering. If the user
  // has a paid endpoint, use it — set ARC_RPC and this picks it up.
  rpcUrls: { default: { http: [process.env.ARC_RPC || 'https://rpc.arc-scan.org'] } },
});

const account = privateKeyToAccount(process.env.PRIVATE_KEY);
const pub = createPublicClient({ chain: arc, transport: http() });
const wallet = createWalletClient({ account, chain: arc, transport: http() });

// Confirm the RPC really is Arc mainnet before anything else. A misconfigured endpoint
// would send the launch — and the money — to a different chain, and there is no undo.
const chainId = await pub.getChainId();
if (chainId !== 5042) throw new Error(`RPC is chain ${chainId}, expected 5042 (Arc mainnet)`);

// Gas is USDC, so the native balance is the spending balance. 18 decimals here.
const balance = await pub.getBalance({ address: account.address });
console.log('wallet', account.address, formatUnits(balance, 18), 'USDC on chain', chainId);
```

If the balance is below the total plus a little for gas, stop and say so. Gas for a launch is
fractions of a cent, but the balance must still cover the exact value.

### 2. Read the live parameters

Never hardcode the launch fee or the tax ceiling — read them, because the Safe can change what
future launches inherit.

```js
const FACTORY = '0xA5b69A713bf53C7f6f2E230428CebA62842fc9e2';

const factoryAbi = parseAbi([
  'struct Metadata { string name; string symbol; string imageUri; string description; string xUrl; string telegramUrl; string websiteUrl; }',
  'struct LaunchOptions { uint16 creatorTaxBps; address creatorFeeRecipient; uint256 devBuyQuote; uint256 devBuyMinTokensOut; bool buyback; address[] snipeTaxExemptions; }',
  'function launchWithNative(Metadata meta, LaunchOptions opts) payable returns (address token, address curve)',
  'function params() view returns (uint256 saleSupply, uint256 virtualQuote, uint256 graduationThreshold, uint256 launchFee, uint16 tradeFeeBps, uint16 creatorFeeShareBps, uint16 maxCreatorTaxBps)',
  'event Launched(address indexed token, address indexed curve, address indexed creator, string name, string symbol, uint256 saleSupply, uint16 creatorTaxBps)',
  // Include the errors and viem decodes a revert into a readable name and arguments
  // instead of a bare selector.
  'error WrongNativeValue(uint256 sent, uint256 needed)',
  'error CreatorTaxTooHigh()',
]);

const [, , , launchFee, , , maxCreatorTaxBps] = await pub.readContract({
  address: FACTORY, abi: factoryAbi, functionName: 'params',
});
```

Reject a creator tax above `maxCreatorTaxBps` before sending, so the user gets a readable message
instead of a revert.

### 3. Build the call

```js
// USDC is 6 decimals as an amount and 18 as msg.value. Every struct field is the 6-decimal one.
// parseUnits takes a STRING: `50.25`, not 50.25. Never build a money amount out of a float —
// Math.round(n * 1e6) drifts, and it drifts silently.
import { parseUnits } from 'viem';
const NATIVE_SCALE = 10n ** 12n;

const devBuy = parseUnits('0', 6);      // whatever the user asked for, '0' if none

const meta = {
  name: 'Example Coin',
  symbol: 'EXMPL',                      // keep it short; this is what traders see
  imageUri: 'ipfs://…',                 // any URL a browser can load, or a data: URI
  description: '',
  xUrl: '', telegramUrl: '', websiteUrl: '',
};

const opts = {
  creatorTaxBps: 0,                     // 0–1000 (0–10%), frozen at launch
  creatorFeeRecipient: account.address, // zero means the launching wallet
  devBuyQuote: devBuy,
  devBuyMinTokensOut: 0n,               // see the note below
  buyback: false,
  snipeTaxExemptions: [],               // max 100, launch-time only
};

const value = (launchFee + devBuy) * NATIVE_SCALE;   // EXACT — see below
```

**The value must be exact.** An under-payment fails with an opaque transfer error. An over-payment
stays in the factory, which has no sweep and no owner withdrawal, so it is gone. Always compute it
from the `params()` you just read.

**`devBuyMinTokensOut`**: the launch buy cannot be front-run — it settles inside the launch
transaction, before the market exists — so `0n` is safe here and is what our own front end sends. Do
not carry that habit over to ordinary buys, where a real floor matters.

### 4. Simulate, then send

Always simulate first. It catches a wrong value, a tax over the cap and a low balance before any
money moves, and it returns the addresses you are about to create.

```js
const { request, result } = await pub.simulateContract({
  address: FACTORY, abi: factoryAbi, functionName: 'launchWithNative',
  account, args: [meta, opts], value,
});
console.log('will create token', result[0], 'curve', result[1]);

const hash = await wallet.writeContract(request);
const receipt = await pub.waitForTransactionReceipt({ hash });
if (receipt.status !== 'success') throw new Error('launch reverted: ' + hash);
```

### 5. Report

Pull the real addresses out of the receipt rather than trusting the simulation, and give the user
the trade link.

```js
import { parseEventLogs } from 'viem';
const [launched] = parseEventLogs({ abi: factoryAbi, eventName: 'Launched', logs: receipt.logs });
console.log('token', launched.args.token);
console.log('curve', launched.args.curve);
console.log('page  https://biddin.win/#terminal:' + launched.args.token);
```

## Choosing the options

**Creator tax.** Default to 0 and only set one if the user asks. It is charged on top of the 1%
protocol fee and is frozen forever. A 5% tax costs a round-tripping buyer 10%, which suppresses the
volume the creator earns 70% of — say this plainly if the user asks for a high number.

**Creator fee recipient.** This is where 70% of every trade fee goes, forever. Only the address
*currently* holding it can pass it on, which is the property that makes launching from a hot wallet
safe — but it also means a typo is unrecoverable. Nobody, including the biddin team, can redirect it
afterwards. Default to the launching wallet by leaving it as `account.address`, and if the user gives
a different address, read it back to them in the confirmation box and make them look at it.

**Buyback and burn.** `buyback: true` wires it up inside the launch transaction, so every creator fee
the coin earns buys the coin back and burns it from the first trade. Doing it afterwards takes two
more transactions and leaves a window where fees went to the wallet instead. If the user wants
buyback at all, do it here.

**Snipe exemptions.** Up to 100 wallets skip the 4-second anti-snipe tax. The factory exempts itself
on every launch — that is how the creator's bundled buy avoids paying the 99% tax — so a receipt
always carries one more `SnipeTaxExempted` event than the user declared. Only add wallets if the user
explicitly asks. If they do, tell them the list is public — each entry emits `SnipeTaxExempted` and
is readable at `curve.snipeTaxExempt(address)` — so buyers and feeds can see it. Do not present it as
a hidden advantage.

## Failure modes worth recognising

| What you see | What it means |
|---|---|
| `CreatorTaxTooHigh()` — selector `0x9ad465dc` | `creatorTaxBps` above `params().maxCreatorTaxBps` |
| `WrongNativeValue(uint256 sent, uint256 needed)` — selector `0xfe018882` | `value` did not equal `(launchFee + devBuyQuote) * 1e12`. Decode it and the two arguments say what you sent and what was required, **in that order** — `sent` first. Both over- and under-payment land here |
| HTTP `503` from the RPC | An `eth_call` carrying `gas` without `from`. Send both or neither — this is not an outage |
| `forge`/tooling prints SUCCESS but nothing happened | Arc pays gas in USDC, so gas estimation fails on transfers. Read the receipt, never the success line |
| Quote looks better than the fill | You used `tradeFeeBps` instead of `totalChargeBps()`, which includes the creator tax |

## Do not

- **Launch a coin that impersonates a real brand, company, project or person.** Anyone can launch
  anything on biddin and no contract checks the name, so this skill is the only place the question
  gets asked. If the requested name, symbol, image or description passes itself off as an official
  token of something real — `USDC`, a listed company, an exchange, a public figure's identity rather
  than a joke about them — say what the problem is and offer a name that is clearly a parody instead.
  A memecoin riffing on something is normal; a coin presenting itself as that thing's official token
  is not, and it is permanent and public the moment it lands.
- Launch without the user's explicit confirmation of the summary in the box above.
- Accept, echo, or store a private key in the conversation.
- Retry a launch that may have already landed. The transaction is not idempotent — a repeat creates a
  second coin and spends the fee again. If a send times out, look up the hash and read the receipt
  before doing anything else.
- Invent a token image. If the user did not supply one, ask; an empty `imageUri` is legal but the coin
  shows as a blank in every feed.
