Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/native-dex.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions frontend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 29 additions & 0 deletions frontend/scripts/boing-native-boing.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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({
Expand Down
10 changes: 6 additions & 4 deletions frontend/src/components/CreateTokenBoingContent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,8 @@ export default function CreateTokenBoingContent() {
<StatusBanner variant="info">
<p className="text-sm mb-0">
Boing token <strong style={{ color: 'var(--text-primary)' }}>v2</strong> — 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.
</p>
</StatusBanner>

Expand All @@ -296,8 +296,8 @@ export default function CreateTokenBoingContent() {
<section className="td-card text-left p-5 sm:p-6" data-testid="create-token-panel">
<h2 className="td-section-title mb-1">Standard</h2>
<p className="text-sm mb-4" style={{ color: 'var(--text-secondary)' }}>
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)' : ''}.
</p>
<div className="grid gap-4 sm:grid-cols-2 mb-4">
<label className="td-field">
Expand Down Expand Up @@ -447,6 +447,8 @@ export default function CreateTokenBoingContent() {
Platform fee {lastResult.platformFee.feeLabel}: {lastResult.platformFee.feeAmount.toString()} tokens
{lastResult.feeCollection?.skipped
? ' — collection deferred (treasury not configured).'
: lastResult.feeCollection?.mintedAtDeploy
? ' — credited to treasury at deploy.'
: lastResult.feeCollection?.ok
? ' — transferred to treasury.'
: lastResult.feeCollection?.ok === false
Expand Down
25 changes: 21 additions & 4 deletions frontend/src/components/NativeBoingCreatePoolPanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -317,7 +318,9 @@ export default function NativeBoingCreatePoolPanel() {
<h3 className="td-section-title mb-1">Create a pool</h3>
<p className="text-sm mb-4" style={{ color: 'var(--text-secondary)' }}>
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.'}
</p>

{projectGateActive ? (
Expand Down Expand Up @@ -607,7 +610,21 @@ export default function NativeBoingCreatePoolPanel() {
</a>
</p>
<ul className="list-none space-y-0.5 pl-0 mb-0">
{lastResult.boingTxIdHex || lastResult.deployTxHash ? (
{lastResult.createAndSeedOneShot && lastResult.addLiquidityTxHash ? (
<li>
1. Create + seed
{lastResult.registerTxHash ? ' + register' : ''} —{' '}
<a
className="text-cyan-400 underline"
href={buildBoingExplorerTxUrl(explorerBaseUrl, lastResult.addLiquidityTxHash)}
target="_blank"
rel="noopener noreferrer"
>
{shortHex(lastResult.addLiquidityTxHash)}
</a>
{lastResult.registerSkipped ? ' (pair listing skipped)' : ''}
</li>
) : lastResult.boingTxIdHex || lastResult.deployTxHash ? (
<li>
1. Deploy —{' '}
<a
Expand All @@ -620,7 +637,7 @@ export default function NativeBoingCreatePoolPanel() {
</a>
</li>
) : null}
{lastResult.postDeployBatched && lastResult.addLiquidityTxHash ? (
{!lastResult.createAndSeedOneShot && lastResult.postDeployBatched && lastResult.addLiquidityTxHash ? (
<li>
2. Bind + seed
{lastResult.registerTxHash ? ' + register' : ''} —{' '}
Expand All @@ -634,7 +651,7 @@ export default function NativeBoingCreatePoolPanel() {
</a>
{lastResult.registerSkipped ? ' (pair listing skipped)' : ''}
</li>
) : (
) : !lastResult.createAndSeedOneShot ? (
<>
{lastResult.setTokensTxHash ? (
<li>
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/components/docs/docsContent.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -309,14 +309,14 @@ function Liquidity() {
<li>
<To to="/create-pool">Create pool</To> starts a new pool: pick two tokens and make the first deposit, which sets
the starting price. Enter amounts the same way as Swap (human units — e.g. <B>200</B> BOING means 200 BOING,
not 200 base units). Express confirms twice: deploy, then one batch that binds tokens, seeds liquidity, and
registers the pair. The pool is registered on the DEX factory, so it shows on Pools and its tokens show in
the swap list right away.
not 200 base units). When the create-and-seed factory is published, Express confirms once (create, seed, and
register in one batch); otherwise it asks twice (deploy, then bind/seed/register). Registered pools show on
Pools and their tokens show in the swap list right away.
</li>
<li>
<To to="/create-token">Create Token</To> deploys a Boing token v2 (whole units, fixed supply at create) —
Standard one-token only. Platform fee is <B>0.5% of initial mint</B> (same target as the bridge fee). After
create, open Create pool with the new token to seed liquidity.
Standard one-token only. Platform fee is <B>0.5% of initial mint</B>, credited to the treasury in the same
deploy approval. After create, open Create pool with the new token to seed liquidity.
</li>
</UL>
</>
Expand Down
35 changes: 35 additions & 0 deletions frontend/src/config/createAndSeedFactory.js
Original file line number Diff line number Diff line change
@@ -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());
}
2 changes: 1 addition & 1 deletion frontend/src/config/createTokenFee.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,5 +126,5 @@ export function createTokenFeeNoticeText(plan) {
if (p.feeAmount <= 0n) {
return `Platform fee: ${rateNote}. Supply is too small for a whole-unit fee cut.`;
}
return `Platform fee: ${rateNote} → ${p.feeAmount.toString()} tokens to the platform treasury after create (separate Express approval).`;
return `Platform fee: ${rateNote} → ${p.feeAmount.toString()} tokens credited to the platform treasury in the same deploy approval (no second Express prompt).`;
}
49 changes: 49 additions & 0 deletions frontend/src/services/boingLiquidityTx.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -23,16 +27,16 @@
function buildAddLiquidityBatchCalls(p) {
const pool = lc(p.poolHex);
const minLp = p.minLp ?? 1n;
const addCd = encodeBoingPoolV8AddLiquidityCalldata(p.amountA, p.amountB, minLp, 0n);

Check warning on line 30 in frontend/src/services/boingLiquidityTx.js

View workflow job for this annotation

GitHub Actions / Lint Frontend

'encodeBoingPoolV8AddLiquidityCalldata' was used before it was defined
if (isNativePair(p)) {
const w = lc(p.wboingHex || WBOING_HEX);

Check warning on line 32 in frontend/src/services/boingLiquidityTx.js

View workflow job for this annotation

GitHub Actions / Lint Frontend

'WBOING_HEX' was used before it was defined
const [boingAmount, otherHex, otherAmount] =
isNativeBoingId(p.tokenAHex) || lc(p.tokenAHex) === w

Check warning on line 34 in frontend/src/services/boingLiquidityTx.js

View workflow job for this annotation

GitHub Actions / Lint Frontend

'isNativeBoingId' was used before it was defined
? [p.amountA, p.tokenBHex, p.amountB]
: [p.amountB, p.tokenAHex, p.amountA];
return [
{ kind: 'contractCallWithValue', contractHex: w, calldata: encodeWboingDepositCalldata(), value: boingAmount },

Check warning on line 38 in frontend/src/services/boingLiquidityTx.js

View workflow job for this annotation

GitHub Actions / Lint Frontend

'encodeWboingDepositCalldata' was used before it was defined
{ kind: 'contractCall', contractHex: w, calldata: encodeReferenceApproveCalldata(pool, boingAmount) },

Check warning on line 39 in frontend/src/services/boingLiquidityTx.js

View workflow job for this annotation

GitHub Actions / Lint Frontend

'encodeReferenceApproveCalldata' was used before it was defined
{ kind: 'contractCall', contractHex: lc(otherHex), calldata: encodeReferenceApproveCalldata(pool, otherAmount) },
{ kind: 'contractCall', contractHex: pool, calldata: addCd },
];
Expand Down Expand Up @@ -101,6 +105,48 @@
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);
Expand All @@ -121,12 +167,14 @@

/** True when the pool pairs native BOING (either side is wBOING). */
const isNativePair = (p) => isNativeBoingId(p.tokenAHex) || isNativeBoingId(p.tokenBHex);
import {

Check failure on line 170 in frontend/src/services/boingLiquidityTx.js

View workflow job for this annotation

GitHub Actions / Lint Frontend

Import in body of module; reorder to top
boingContractCallToExpressTx,
boingPoolBytecodeHex,
boingTxToExpressRequest,
buildBatchTransaction,
buildBoingPoolCallTransaction,
buildBoingTokenApproveTransaction,
buildCreateAndSeedBatchTransaction,
encodeBoingPoolV8AddLiquidityCalldata,
encodeBoingPoolV8RemoveLiquidityCalldata,
encodeNativeAmmSetTokensCalldata,
Expand All @@ -135,9 +183,10 @@
encodeWboingDepositCalldata,
encodeWboingWithdrawAllCalldata,
getBoingTokenAllowance,
hexToBytes,
} from '@boing-network/boing-sdk';
import { WBOING_HEX, isNativeBoingId } from './boingSwapQuote.js';

Check failure on line 188 in frontend/src/services/boingLiquidityTx.js

View workflow job for this annotation

GitHub Actions / Lint Frontend

Import in body of module; reorder to top
import { withBoingExpressDisplay } from './boingExpressTxDisplay.js';

Check failure on line 189 in frontend/src/services/boingLiquidityTx.js

View workflow job for this annotation

GitHub Actions / Lint Frontend

Import in body of module; reorder to top

/**
* @param {import('@boing-network/boing-sdk').BoingClient} client
Expand Down
Loading
Loading