From d44b679f0f9f46e1bae6fc04f8c78a39e8a32b68 Mon Sep 17 00:00:00 2001 From: Nicholas Chikuji <61144399+chiku524@users.noreply.github.com> Date: Thu, 5 Nov 2026 21:26:40 +0000 Subject: [PATCH 1/2] feat: one-shot create-and-seed pool create + mint-at-deploy token fee Wire Create Pool to the create-and-seed factory when published (one Express approval). Credit Create Token platform fee in deploy init so fee collection no longer needs a second approval. Co-authored-by: nico.builds --- docs/native-dex.md | 2 +- frontend/.env.example | 3 + frontend/scripts/boing-native-boing.test.mjs | 29 +++++++ .../components/NativeBoingCreatePoolPanel.jsx | 25 ++++++- frontend/src/components/docs/docsContent.jsx | 10 +-- frontend/src/config/createAndSeedFactory.js | 35 +++++++++ frontend/src/services/boingLiquidityTx.js | 49 ++++++++++++ .../src/services/boingNativeCreatePool.js | 75 +++++++++++++++++-- .../src/services/boingNativeCreateToken.js | 22 +++++- frontend/src/services/boingTokenV2Launch.js | 66 +++++++++++++--- 10 files changed, 285 insertions(+), 31 deletions(-) create mode 100644 frontend/src/config/createAndSeedFactory.js diff --git a/docs/native-dex.md b/docs/native-dex.md index ff5889a0..f4ddb11a 100644 --- a/docs/native-dex.md +++ b/docs/native-dex.md @@ -41,7 +41,7 @@ There is **no compiler switch** that turns Solidity into Boing VM bytecode. (The - `nativeVm.dexFactory`, `swapRouter`, `ledgerRouterV2` / `V3`, `liquidityLocker`, plus LP vault / share token ids (env or live RPC; zeros when unpublished). - Runtime defaults come from **`fetchNativeDexIntegrationDefaults`** (`@boing-network/boing-sdk`): live `boing_getNetworkInfo.end_user` plus env overrides. Historical SDK-embedded 6913 hexes are **not** treated as live. -The **Swap** tab on 6913 targets the **network default** constant-product pool. **Create Pool** on 6913 deploys a new CP pool, then one Express batch binds token ids, seeds `add_liquidity`, and runs `register_pair` when a factory id is published (two user approvals — deploy cannot join a protocol batch). Amounts are human units (same as Swap). **Pools → Your liquidity** add/remove (Advanced section for a pasted id) works on the canonical pool, a directory venue, or a pasted pool AccountId. Additional factory pairs (including multihop) are surfaced under **Pools** and **Smart route**. +The **Swap** tab on 6913 targets the **network default** constant-product pool. **Create Pool** on 6913 prefers the **create-and-seed** factory when `REACT_APP_BOING_NATIVE_CREATE_AND_SEED_FACTORY` is set: one Express Batch CREATE2s the pool, binds tokens, seeds `add_liquidity`, and `register_pair`s on the directory (one user approval). Without that factory, Create Pool falls back to deploy + one bind/seed/register batch (two approvals — deploy cannot join a protocol batch). Amounts are human units (same as Swap). **Pools → Your liquidity** add/remove (Advanced section for a pasted id) works on the canonical pool, a directory venue, or a pasted pool AccountId. Additional factory pairs (including multihop) are surfaced under **Pools** and **Smart route**. ### “Deploy the Solidity DEX on Boing” actually means diff --git a/frontend/.env.example b/frontend/.env.example index 89122e05..c4dd7985 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -56,6 +56,9 @@ REACT_APP_BOING_TESTNET_RPC_URL=https://testnet-rpc.boing.network/ # REACT_APP_BOING_NATIVE_AMM_POOL=0x… # REACT_APP_BOING_NATIVE_VM_DEX_FACTORY=0x… +# Create-and-seed factory (CREATE2 pool + bind + optional register). When set, Create Pool asks +# for one Express approval. Deploy `native_dex_create_and_seed_bytecode` from boing.network first. +# REACT_APP_BOING_NATIVE_CREATE_AND_SEED_FACTORY=0x… # Linked NFT collection ↔ fungible token registry (32-byte AccountId). Required for on-chain # claim/register/unlink on /linked-project; also accepts end_user.canonical_linked_nft_token_registry. # Registry v2 (CREATE2 salt BOING_NFT_TOKEN_LINK_REG_V2, block 1174): an asset is claimed by its creator in its own diff --git a/frontend/scripts/boing-native-boing.test.mjs b/frontend/scripts/boing-native-boing.test.mjs index 63c7f2eb..b7e9cc9e 100644 --- a/frontend/scripts/boing-native-boing.test.mjs +++ b/frontend/scripts/boing-native-boing.test.mjs @@ -19,10 +19,12 @@ import { } from '../src/services/boingSwapQuote.js'; import { poolCard, pairedAmount } from '../src/services/boingPoolsSimple.js'; import { + buildCreateAndSeedOneShotBatchRequest, buildCreatePoolPostDeployBatchRequest, buildNativeAddLiquidityRequest, buildNativeRemoveLiquidityRequest, } from '../src/services/boingLiquidityTx.js'; +import { boingPoolBytecodeHex } from '@boing-network/boing-sdk'; import { planNativeBoingSwap } from '../src/services/nativeDexCustodySwap.js'; const USDC = '0x6676f971ade3982406efa3e2cdb0477923f8f2a15624c4b6ad3a432817a75e93'; @@ -106,6 +108,33 @@ test('native add / remove liquidity are single batches (wrap with value, unwrap assert.ok(rm.access_list.write.includes(WBOING_HEX)); }); +test('create-and-seed one-shot batch: wrap? + approves + create_and_seed + add_liquidity', () => { + const CAS = `0x${'c5'.repeat(32)}`; + const DIR = `0x${'cd'.repeat(32)}`; + const salt = `0x${'07'.repeat(32)}`; + const { poolHex, request } = buildCreateAndSeedOneShotBatchRequest({ + factoryHex: CAS, + saltHex32: salt, + tokenAHex: WBOING_HEX, + tokenBHex: USDC, + amountA: E18, + amountB: 200n, + senderHex: ME, + directoryHex: DIR, + poolBytecodeHex: boingPoolBytecodeHex(8), + }); + assert.equal(request.type, 'batch'); + assert.match(poolHex, /^0x[0-9a-f]{64}$/); + // deposit + approve wBOING + approve USDC + create_and_seed + add_liquidity + assert.equal(request.calls.length, 5); + assert.equal(request.calls[0].contract, WBOING_HEX); + assert.equal(request.calls[0].value, String(E18)); + assert.equal(request.calls[3].contract, CAS); + assert.equal(request.calls[4].contract, poolHex); + assert.ok(request.access_list.write.includes(CAS)); + assert.ok(request.access_list.write.includes(DIR)); +}); + test('create-pool post-deploy seed is one batch: set_tokens + wrap/approves/add + optional register', () => { const FACTORY = `0x${'cd'.repeat(32)}`; const withReg = buildCreatePoolPostDeployBatchRequest({ diff --git a/frontend/src/components/NativeBoingCreatePoolPanel.jsx b/frontend/src/components/NativeBoingCreatePoolPanel.jsx index a4e21b88..f439420b 100644 --- a/frontend/src/components/NativeBoingCreatePoolPanel.jsx +++ b/frontend/src/components/NativeBoingCreatePoolPanel.jsx @@ -30,6 +30,7 @@ import { showDeployCelebration } from '../utils/deployCelebration'; import { buildBoingExplorerAccountUrl, buildBoingExplorerTxUrl } from '../config/boingExplorerUrls'; import { isBoingNativeAccountIdHex } from '../utils/boingWalletDiscovery'; import { normalizeNativeVmTokenId32 } from '../services/nativeVmTokenRegistry'; +import { isCreateAndSeedFactoryConfigured } from '../config/createAndSeedFactory'; const DEFAULT_POOL_PURPOSE = 'dapp'; @@ -317,7 +318,9 @@ export default function NativeBoingCreatePoolPanel() {

Create a pool

Pick two tokens and deposit the first liquidity. That first deposit sets the starting price. Boing Express will - ask twice: deploy the pool, then one batch that binds tokens, seeds liquidity, and registers the pair. + {isCreateAndSeedFactoryConfigured() + ? ' ask once: create the pool, seed liquidity, and register the pair in a single batch.' + : ' ask twice: deploy the pool, then one batch that binds tokens, seeds liquidity, and registers the pair.'}

{projectGateActive ? ( @@ -603,7 +606,21 @@ export default function NativeBoingCreatePoolPanel() {

diff --git a/frontend/src/config/createAndSeedFactory.js b/frontend/src/config/createAndSeedFactory.js new file mode 100644 index 00000000..8c87ae8b --- /dev/null +++ b/frontend/src/config/createAndSeedFactory.js @@ -0,0 +1,35 @@ +/** + * Create-and-seed factory AccountId (CREATE2 pool + bind + optional register in one call). + * When set, Create Pool uses a single Express Batch instead of deploy + seed. + * Deploy the factory from boing.network (`native_dex_create_and_seed_bytecode`) and publish here. + */ + +function readEnv(name) { + try { + if (typeof process !== 'undefined' && process.env && typeof process.env[name] === 'string') { + return process.env[name].trim(); + } + } catch { + /* ignore */ + } + return ''; +} + +/** Normalize 32-byte AccountId hex (optional 0x) → lowercase `0x`+64 or ''. */ +export function normalizeCreateAndSeedFactoryHex(raw) { + const t = String(raw ?? '').trim(); + if (!t) return ''; + const hex = t.startsWith('0x') || t.startsWith('0X') ? t.slice(2) : t; + if (!/^[0-9a-fA-F]{64}$/.test(hex) || /^0+$/.test(hex)) return ''; + return `0x${hex.toLowerCase()}`; +} + +/** @returns {string} factory AccountId or '' when unset / invalid */ +export function getCreateAndSeedFactoryHex() { + return normalizeCreateAndSeedFactoryHex(readEnv('REACT_APP_BOING_NATIVE_CREATE_AND_SEED_FACTORY')); +} + +/** True when Finance can offer one-approval create-and-seed. */ +export function isCreateAndSeedFactoryConfigured() { + return Boolean(getCreateAndSeedFactoryHex()); +} diff --git a/frontend/src/services/boingLiquidityTx.js b/frontend/src/services/boingLiquidityTx.js index 42b52aed..7d14f2a7 100644 --- a/frontend/src/services/boingLiquidityTx.js +++ b/frontend/src/services/boingLiquidityTx.js @@ -11,6 +11,10 @@ * set_tokens -> (wrap/approves/)add_liquidity -> optional factory register_pair * so Express asks twice (deploy + seed), not four times. Deploy cannot join a batch: Express/protocol * batch steps are transfer | contract_call only. + * + * When a create-and-seed factory is published (`REACT_APP_BOING_NATIVE_CREATE_AND_SEED_FACTORY`), + * Create Pool uses ONE batch instead: wrap? → approve → create_and_seed (CREATE2+bind+register) → + * add_liquidity — no top-level pool deploy. */ const lc = (h) => String(h || '').toLowerCase(); @@ -101,6 +105,48 @@ export function buildCreatePoolPostDeployBatchRequest(p) { return boingTxToExpressRequest(tx); } +/** + * True one-approval Create Pool when the create-and-seed factory is published. + * @param {{ + * factoryHex: string, saltHex32: string, tokenAHex: string, tokenBHex: string, + * amountA: bigint, amountB: bigint, senderHex: string, minLp?: bigint, + * directoryHex?: string | null, poolBytecodeHex?: string, wboingHex?: string, + * }} p + */ +export function buildCreateAndSeedOneShotBatchRequest(p) { + const tokenA = lc(p.tokenAHex); + const tokenB = lc(p.tokenBHex); + const w = lc(p.wboingHex || WBOING_HEX); + const poolBytecode = hexToBytes(p.poolBytecodeHex || boingPoolBytecodeHex(8)); + let wrap = null; + let amountA = p.amountA; + let amountB = p.amountB; + let legA = tokenA; + let legB = tokenB; + if (isNativeBoingId(tokenA) || tokenA === w) { + wrap = { contractHex: w, calldata: encodeWboingDepositCalldata(), value: amountA }; + legA = w; + } else if (isNativeBoingId(tokenB) || tokenB === w) { + wrap = { contractHex: w, calldata: encodeWboingDepositCalldata(), value: amountB }; + legB = w; + } + const { poolHex, tx } = buildCreateAndSeedBatchTransaction({ + senderHex: p.senderHex, + factoryHex: p.factoryHex, + saltHex32: p.saltHex32, + tokenAHex: legA, + tokenBHex: legB, + amountA, + amountB, + minLp: p.minLp ?? 1n, + directoryHex: p.directoryHex || null, + poolBytecode, + wrap, + nonce: 0n, + }); + return { poolHex, request: boingTxToExpressRequest(tx) }; +} + /** Batch request for a native-BOING remove (exported for tests). */ export function buildNativeRemoveLiquidityRequest(p) { const w = lc(p.wboingHex || WBOING_HEX); @@ -123,10 +169,12 @@ export function buildNativeRemoveLiquidityRequest(p) { const isNativePair = (p) => isNativeBoingId(p.tokenAHex) || isNativeBoingId(p.tokenBHex); import { boingContractCallToExpressTx, + boingPoolBytecodeHex, boingTxToExpressRequest, buildBatchTransaction, buildBoingPoolCallTransaction, buildBoingTokenApproveTransaction, + buildCreateAndSeedBatchTransaction, encodeBoingPoolV8AddLiquidityCalldata, encodeBoingPoolV8RemoveLiquidityCalldata, encodeNativeAmmSetTokensCalldata, @@ -135,6 +183,7 @@ import { encodeWboingDepositCalldata, encodeWboingWithdrawAllCalldata, getBoingTokenAllowance, + hexToBytes, } from '@boing-network/boing-sdk'; import { WBOING_HEX, isNativeBoingId } from './boingSwapQuote.js'; import { withBoingExpressDisplay } from './boingExpressTxDisplay.js'; diff --git a/frontend/src/services/boingNativeCreatePool.js b/frontend/src/services/boingNativeCreatePool.js index 5656cf40..e1617c94 100644 --- a/frontend/src/services/boingNativeCreatePool.js +++ b/frontend/src/services/boingNativeCreatePool.js @@ -7,11 +7,20 @@ import { encodeNativeDexRegisterPairCalldataHex } from './nativeDexFactoryCallda import { nativeAccountsAccessListJson } from './nativeAmmAccessList'; import { normalizeNativeVmTokenId32 } from './nativeVmTokenRegistry'; import { formatBoingExpressRpcError } from '../utils/boingExpressRpcError'; -import { boingPoolBytecodeHex, predictNonceDerivedContractAddress } from '@boing-network/boing-sdk'; -import { buildCreatePoolPostDeployBatchRequest, executeAddLiquidity } from './boingLiquidityTx'; +import { + boingPoolBytecodeHex, + predictNonceDerivedContractAddress, + randomCreateAndSeedPoolSaltHex, +} from '@boing-network/boing-sdk'; +import { + buildCreateAndSeedOneShotBatchRequest, + buildCreatePoolPostDeployBatchRequest, + executeAddLiquidity, +} from './boingLiquidityTx'; import { withBoingExpressDisplay } from './boingExpressTxDisplay'; import { getSharedBoingClient } from './boingNativeDexClient'; import { boingWalletMissingMessage } from '../utils/boingWalletDiscovery'; +import { getCreateAndSeedFactoryHex } from '../config/createAndSeedFactory'; const U64_MAX = (1n << 64n) - 1n; @@ -236,6 +245,64 @@ export async function createBoingNativeConstantProductPool({ return { ok: false, code: 'no_provider', message: boingWalletMissingMessage() }; } + // No override -> custody pool v8: token-v2 pairs pulled with approvals, quoted and swapped by the swap card. + const custody = !String(customBytecode || '').trim(); + const createAndSeedFactory = custody ? getCreateAndSeedFactoryHex() : ''; + const factory = normalizeNativeVmTokenId32(factoryHex || ''); + + // True one-approval path: CREATE2 factory + Batch (approve → create_and_seed → add_liquidity). + if (createAndSeedFactory) { + try { + const saltHex32 = randomCreateAndSeedPoolSaltHex(); + const { poolHex, request } = buildCreateAndSeedOneShotBatchRequest({ + factoryHex: createAndSeedFactory, + saltHex32, + tokenAHex: tokenA, + tokenBHex: tokenB, + amountA: amountABn, + amountB: amountBBn, + minLp: 1n, + senderHex: account, + directoryHex: factory || null, + poolBytecodeHex: boingPoolBytecodeHex(8), + }); + const batchTx = withBoingExpressDisplay( + request, + factory + ? 'Create pool, seed liquidity, and register the pair' + : 'Create pool and seed liquidity' + ); + const oneShotHash = await boingExpressContractCallSignSimulateSubmit(provider, batchTx, { + senderHex: account, + waitForInclusion: true, + }); + if (!factory) { + warnings.push( + 'No native DEX directory id is published on this network, so the pair was not registered for discovery.' + ); + } + return { + ok: true, + poolId: poolHex, + deployTxHash: oneShotHash, + boingTxIdHex: oneShotHash, + setTokensTxHash: oneShotHash, + addLiquidityTxHash: oneShotHash, + registerTxHash: factory ? oneShotHash : null, + setTokensSkipped: false, + registerSkipped: !factory, + postDeployBatched: true, + createAndSeedOneShot: true, + custody: true, + warnings, + }; + } catch (e) { + warnings.push( + `Create-and-seed one-shot failed (${formatBoingExpressRpcError(e)}); falling back to deploy + bind/seed.` + ); + } + } + // Pool templates emit no deploy log, so the receipt alone does not name the new account: predict the // nonce-derived address up front and confirm it has code once the deploy is included. let predictedPoolId = null; @@ -245,8 +312,6 @@ export async function createBoingNativeConstantProductPool({ predictedPoolId = null; } - // No override -> custody pool v8: token-v2 pairs pulled with approvals, quoted and swapped by the swap card. - const custody = !String(customBytecode || '').trim(); const deploy = await executeBoingLaunchWizardDeploy({ kind: 'liquidity_pool', getWalletProvider, @@ -279,8 +344,6 @@ export async function createBoingNativeConstantProductPool({ }; } - const factory = normalizeNativeVmTokenId32(factoryHex || ''); - // Custody: one Express approval for bind + seed (+ register). Custom bytecode keeps the sequential path. if (custody) { try { diff --git a/frontend/src/services/boingNativeCreateToken.js b/frontend/src/services/boingNativeCreateToken.js index cddd8b10..582ef572 100644 --- a/frontend/src/services/boingNativeCreateToken.js +++ b/frontend/src/services/boingNativeCreateToken.js @@ -5,9 +5,9 @@ * marks mint_first used, and (when configured) claims the token in the linked NFT↔token registry. * * Platform fee (percent of initial mint) is planned in {@link planCreateTokenPlatformFee}. - * When a fee recipient is configured and feeAmount > 0, a follow-up reference-token transfer - * collects the fee share (separate Express approval). Live builds use the committed treasury - * AccountId (overridable via `REACT_APP_CREATE_TOKEN_FEE_RECIPIENT`). + * When a fee recipient is configured and feeAmount > 0, the fee is credited to the treasury + * inside the mint-at-deploy init (same Express approval as create). Live builds use the committed + * treasury AccountId (overridable via `REACT_APP_CREATE_TOKEN_FEE_RECIPIENT`). */ import { bytesToHex, @@ -203,6 +203,10 @@ export async function createBoingNativeToken({ descriptionHashHex: input.descriptionHashHex, runtimeHexOverride: input.bytecodeHexOverride, claimRegistryHex, + // One-shot fee: mint treasury share in deploy init when collection is ready. + feeRecipientHex: + collectPlatformFee && platformFee.collectionReady ? platformFee.recipient : null, + feeAmount: collectPlatformFee && platformFee.collectionReady ? platformFee.feeAmount : 0n, }); } catch (e) { return { ok: false, code: 'prepare_failed', message: e?.message || 'Could not prepare the deploy.', platformFee }; @@ -278,7 +282,17 @@ export async function createBoingNativeToken({ }); let feeCollection; - if (collectPlatformFee && platformFee.collectionReady) { + if (launch.feeMintedAtDeploy) { + feeCollection = { + ok: true, + skipped: false, + mintedAtDeploy: true, + feePlan: platformFee, + txHash, + boingTxIdHex, + }; + } else if (collectPlatformFee && platformFee.collectionReady) { + // Legacy fallback if mint-at-deploy fee was not applied (e.g. custom runtime override). feeCollection = await collectCreateTokenPlatformFee({ getWalletProvider, account: sender, diff --git a/frontend/src/services/boingTokenV2Launch.js b/frontend/src/services/boingTokenV2Launch.js index 175b255d..ed24f923 100644 --- a/frontend/src/services/boingTokenV2Launch.js +++ b/frontend/src/services/boingTokenV2Launch.js @@ -136,16 +136,32 @@ export function buildBoingTokenV2LaunchTx(input) { if (nonce < 0n) throw new Error('nonce must be >= 0'); const supply = parseBoingTokenV2Supply(input.supply); const tokenHex = predictNonceDerivedContractAddress(senderHex, nonce).toLowerCase(); + const feeRecipientHex = String(input.feeRecipientHex ?? '').trim() || null; + const feeAmount = + typeof input.feeAmount === 'bigint' + ? input.feeAmount + : input.feeAmount != null + ? BigInt(input.feeAmount) + : 0n; const bytecode = buildBoingTokenV2MintAtDeployBytecodeHex({ supply, runtimeHex: String(input.runtimeHexOverride ?? '').trim() || undefined, claimRegistryHex: input.claimRegistryHex || null, + feeRecipientHex, + feeAmount, }); const deployTx = { ...buildBoingTokenV2DeployMetaTx({ ...input, bytecodeHexOverride: bytecode }), nonce: nonce.toString(), }; - return { tokenHex, supply, deployTx }; + return { + tokenHex, + supply, + deployTx, + feeMintedAtDeploy: Boolean(feeRecipientHex && feeAmount > 0n), + feeAmount, + feeRecipientHex: feeRecipientHex ? validateHex32(feeRecipientHex).toLowerCase() : null, + }; } /* ------------------------------------------------------------------------------------------------ @@ -168,14 +184,33 @@ export const BOING_TOKEN_MINT_ONCE_SLOT_HEX = `0x${'00'.repeat(31)}f1`; export { BOING_DEPLOY_INIT_MARKER }; /** - * `0xFD || init` deploy bytecode: v2 runtime with the whole `supply` minted to the deployer at deploy. - * With `claimRegistryHex` the init also calls the linked NFT↔token registry v2 `claim_self(deployer)` - * (the registry faults, and so the whole deploy fails, if the token were somehow already claimed). - * @param {{ supply: unknown, runtimeHex?: string, balanceXorMask?: Uint8Array, claimRegistryHex?: string | null }} input + * `0xFD || init` deploy bytecode: v2 runtime with supply minted at deploy. + * When `feeRecipientHex` + `feeAmount` are set, credits `supply - fee` to the deployer and `fee` to + * the treasury in the same init (one Express approval; no post-create transfer). + * With `claimRegistryHex` the init also calls the linked NFT↔token registry v2 `claim_self(deployer)`. + * @param {{ supply: unknown, runtimeHex?: string, balanceXorMask?: Uint8Array, claimRegistryHex?: string | null, + * feeRecipientHex?: string | null, feeAmount?: bigint }} input * @returns {`0x${string}`} */ -export function buildBoingTokenV2MintAtDeployBytecodeHex({ supply, runtimeHex, balanceXorMask, claimRegistryHex = null } = {}) { +export function buildBoingTokenV2MintAtDeployBytecodeHex({ + supply, + runtimeHex, + balanceXorMask, + claimRegistryHex = null, + feeRecipientHex = null, + feeAmount = 0n, +} = {}) { const amount = parseBoingTokenV2Supply(supply); + let fee = typeof feeAmount === 'bigint' ? feeAmount : BigInt(feeAmount || 0); + if (fee < 0n) throw new Error('feeAmount must be >= 0'); + if (fee > amount) throw new Error('feeAmount cannot exceed supply'); + let feeTo = null; + if (fee > 0n) { + feeTo = validateHex32(feeRecipientHex || '').toLowerCase(); + } else { + fee = 0n; + } + const creatorAmount = amount - fee; const runtime = runtimeBytesForInit(runtimeHex || boingTokenV2BytecodeHex()); const mask = balanceXorMask || REFERENCE_FUNGIBLE_BALANCE_XOR; if (!(mask instanceof Uint8Array) || mask.length !== 32) throw new Error('balance XOR mask must be 32 bytes'); @@ -184,11 +219,20 @@ export function buildBoingTokenV2MintAtDeployBytecodeHex({ supply, runtimeHex, b w.op(OP.CALLER); w.push32(hexToU8(BOING_TOKEN_ADMIN_SLOT_HEX)); w.op(OP.SSTORE); - // balance[CALLER ^ mask] = supply - w.push32(word(amount)); - w.op(OP.CALLER); - w.push32(mask); - w.op(OP.XOR, OP.SSTORE); + // balance[CALLER ^ mask] = creatorAmount (full supply when no fee) + if (creatorAmount > 0n) { + w.push32(word(creatorAmount)); + w.op(OP.CALLER); + w.push32(mask); + w.op(OP.XOR, OP.SSTORE); + } + // balance[treasury ^ mask] = fee + if (fee > 0n && feeTo) { + w.push32(word(fee)); + w.push32(hexToU8(feeTo)); + w.push32(mask); + w.op(OP.XOR, OP.SSTORE); + } // mint-once = 1 w.push32(word(1n)); w.push32(hexToU8(BOING_TOKEN_MINT_ONCE_SLOT_HEX)); From a0ab6aceeee4529355492dcc7310ef710398dbf9 Mon Sep 17 00:00:00 2001 From: Nicholas Chikuji <61144399+chiku524@users.noreply.github.com> Date: Thu, 5 Nov 2026 21:38:01 +0000 Subject: [PATCH 2/2] fix(ux): align Create Token copy with mint-at-deploy fee One Express approval credits the platform fee at deploy; stop promising a separate fee transfer. Also honest Create Pool success when register is skipped. Co-authored-by: nico.builds --- frontend/src/components/CreateTokenBoingContent.jsx | 10 ++++++---- frontend/src/components/NativeBoingCreatePoolPanel.jsx | 6 +++++- frontend/src/config/createTokenFee.js | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/CreateTokenBoingContent.jsx b/frontend/src/components/CreateTokenBoingContent.jsx index 9a9f0fa6..5f1374ce 100644 --- a/frontend/src/components/CreateTokenBoingContent.jsx +++ b/frontend/src/components/CreateTokenBoingContent.jsx @@ -272,8 +272,8 @@ export default function CreateTokenBoingContent() {

Boing token v2 — one Express signature creates - the token and puts the whole supply in your account. Nobody can mint more afterwards. Add a pool next to - make it swappable. + the token. Your wallet receives the mint minus the platform fee (fee goes to treasury at deploy). Nobody + can mint more afterwards. Add a pool next to make it swappable.

@@ -296,8 +296,8 @@ export default function CreateTokenBoingContent() {

Standard

- Create one fungible token. Express will ask you to approve the deploy - {feePreview.collectionReady ? ', then a separate fee transfer' : ''}. + Create one fungible token. Express will ask once to approve the deploy + {feePreview.collectionReady ? ' (platform fee is credited to treasury in that same approval)' : ''}.