diff --git a/Parameters/ASM DEX/ASM-Multichain.md b/Parameters/ASM DEX/ASM-Multichain.md index c68bc5d..200638a 100644 --- a/Parameters/ASM DEX/ASM-Multichain.md +++ b/Parameters/ASM DEX/ASM-Multichain.md @@ -49,9 +49,11 @@ carries its claim back on the source chain and is redeemable. This is the single-party, oracle-attested sibling of an atomic swap — a **lock-and-mint** bridge. -Examples: lock 1 ETH on Ethereum → mint a transferable **εETH** CashToken on BCH; -lock 1 USDT on Tron → mint **τUSDT**. The wrapped token's prefix marks its *source -chain* (see [naming](#wrapped-asset-naming--one-marker-per-source-chain) below). +Examples: lock 1 USDt on Ethereum → mint a transferable **USDt** CashToken on BCH +(displayed `USDt · Ethereum`); lock 1 USDt on Tron → mint a *distinct* **USDt** +(`USDt · Tron`). Same human name, **different CashToken categories** — the chain +rides as a badge, with `εUSDt`/`τUSDt` as the bare-string fallback (see +[naming](#wrapped-asset-naming) below). ## Flow @@ -131,30 +133,40 @@ per chain, ADR-0035) stays intact. | "Redeem anywhere" | native, but heavy & higher-trust | emergent from liquidity, automatable | | Works for USDT on BCH? | no (you don't issue USDT; no CCTP on BCH) | yes | -## Wrapped-asset naming — one marker per source chain +## Wrapped-asset naming -Every VM-script chain can be a source, and all of them mint onto Bitcoin Cash as -CashTokens. To keep the *same* token wrapped from *different* chains distinct, the -IOU carries a **Greek prefix matching its source chain's initial**: +Distinguishing which chain an IOU belongs to is **three separate layers** — keep +them separate (identity → name+badge → text-only fallback): -| Source chain | Marker | Wrapped examples | -|---|---|---| -| Ethereum | **ε** | εETH, εUSDT | -| Tron | **τ** | τTRX, τUSDT | +1. **Identity = the CashToken category.** Every `(source chain, asset)` IOU is a + *distinct* category (the genesis txid, baked into a per-chain covenant). + Tron-USDt and Ethereum-USDt are **unforgeably different tokens** — they can + never merge or be confused at the protocol level, *whatever you call them*. The + category is the source of truth; every name below is just display over it. +2. **Human name = the asset + a network badge.** The displayed ticker stays the + familiar **`USDt`**; the chain rides as **BCMR metadata + a mandatory network + badge** — `USDt · Tron`, with a chain logo — exactly how exchanges + (`USDT (TRC-20)` vs `(ERC-20)`) and wallets render the same USDT across chains. + **Anywhere there's room for a badge, use the badge.** +3. **Greek prefix = text-only fallback.** For **bare-string surfaces** where no + badge can render — order-book rows, receipts, logs, compact Activity entries — + use a self-contained source-chain Greek prefix: -So `εUSDT` (USDT wrapped from Ethereum) and `τUSDT` (USDT wrapped from Tron) are -**different CashTokens** with different backing locks — never fungible with each -other. The prefix is part of the token's identity: the covenant that mints `εETH` -is a different contract, gated by an attestation of an *Ethereum* lock, from the -one that mints `τTRX`. New source chains extend the table with their own marker. + | Source chain | Prefix | Bare-string form | + |---|---|---| + | Ethereum | **ε** | εUSDt, εUSDC, εETH | + | Tron | **τ** | τUSDt, τTRX | -**Why a prefix, not a suffix.** The marker leads the ticker so it never collides -with the base symbol's own letters — a suffix would give `USDTτ` ("USDT" already -ends in *t*, doubling it) and `USDTε` reads as "USDTe". Leading the marker keeps -the base asset legible and groups the chain badge up front; in UI it renders as a -small `ε`/`τ` pill before the ticker. It also reads as a **namespace** — the chain -*owns* the asset (`ε ∷ USDT` = "Ethereum's USDT"), the ticker a leaf under it, -like a path — which is the natural way to say "this IOU belongs to chain X." + The prefix *leads* the ticker so it never collides with the base symbol's + letters (a suffix gives `USDtτ` — doubled *t* — and `USDtε` reads "USDte"), and + it reads as a namespace (`ε ∷ USDt` = "Ethereum's USDt"). + +**Rule of thumb:** name `USDt` + badge where there's room; `εUSDt` where there +isn't. You don't *need* the prefix to distinguish chains — the category + a +network tag already does — so the prefix is never the *only* thing telling two +IOUs apart; it's just the compact label when a badge can't be shown. (Mockup: the +ASM·Multichain mint row shows `USDt` + an `Ethereum` badge; the button/stepper use +the `εUSDt` fallback.) ## Trust model — honest boundary diff --git a/Parameters/ASM DEX/README.md b/Parameters/ASM DEX/README.md index 34f6312..a678eb6 100644 --- a/Parameters/ASM DEX/README.md +++ b/Parameters/ASM DEX/README.md @@ -72,9 +72,11 @@ Both move value across chains, but they fall on opposite sides of the SP/P2P line: - **ASM · Multichain** = single-party, **no counterparty**. You lock under a VM - condition; an oracle attests; a covenant mints a transferable IOU, marked by its - **source chain** (`εETH`, `τTRX` — see - [ASM-Multichain](ASM-Multichain.md#wrapped-asset-naming--one-marker-per-source-chain)). + condition; an oracle attests; a covenant mints a transferable IOU whose identity + is its **CashToken category** (one per source chain); displayed as the asset name + + a network badge (`USDt · Tron`), with a `τ`/`ε` prefix as the text-only + fallback (see + [naming](ASM-Multichain.md#wrapped-asset-naming)). Trust = the lock VM + the oracle (not a person). Requires the source chain to support **VM scripts**. - **DEX · Atomic swap** = two-party, **needs a counterparty**, but **trustless** diff --git a/Parameters/ASM DEX/cross-chain-asm-concept-and-state.md b/Parameters/ASM DEX/cross-chain-asm-concept-and-state.md new file mode 100644 index 0000000..605c001 --- /dev/null +++ b/Parameters/ASM DEX/cross-chain-asm-concept-and-state.md @@ -0,0 +1,267 @@ +# Cross-chain ASM — concept, conclusions, and build state + +**Date:** 2026-06-23 (covers the 2026-06-21 → 06-23 work + the design discussion) +**Scope:** the cross-chain ASM (lock → oracle → mint IOU on BCH), its place in the +ASM/DEX taxonomy, the trust analysis that grounds it, and what is built/validated. + +This is the synthesis record. Per-cell detail lives in `ASM-Multichain.md`, +`README.md`, etc. This file captures the *concept*, the *conclusions*, and the +*state*, so the reasoning survives a context compact. + +--- + +## 1. The concept (the goal) + +A **permissionless, non-custodial onramp for stablecoin liquidity into Bitcoin +Cash** — and a market that prices the project's own money against it. + +- Open the gates of crypto stablecoins into BCH **without friction, middlemen, + custody, CEXs, or permission.** Users interact directly, cross-chain, on-chain, + non-custodially, without asking anyone. +- **Zero authority, zero custody, zero friction, zero middleman, zero + counterparty** — and *trust-minimized* (a bounded, auditable oracle is the one + residual, on a path to trustless). +- The endpoint = the original goal: **external money arrives from any VM-script + chain → the AMM/DEX lets the market value the FIATm/USDm (MBA) assets.** Value + *discovered by a market*, not *asserted by an issuer*. + +**The permission moat.** Every existing path to get stablecoins onto a chain is +gatekept: the **issuer** must decide to mint there (Circle/Tether haven't, won't +soon), a **CEX** must list it (fees, custody), **CCTP/IBC don't reach BCH.** So +BCH waits for a permission that may never come. The **trustless wrap is the one +gate nobody can refuse** — you don't ask, you lock-and-mint. That un-gatekeepability +is the moat. + +--- + +## 2. Architecture — two halves of one machine + +- **Build B — the gate.** Cross-chain wrap: lock an asset on a source VM-script + chain (Tron/Ethereum) → mint a transferable IOU on BCH. Counterparty-free, + non-custodial. +- **Build A — the market.** The first-party AMM/DEX (`SharePairAMM`) where the + arriving money *prices* USDm/FIATm. +- **BCH as the hub.** Money arrives from any chain → is market-valued against + USDm → leaves to any chain. Multi-chain routing is done by a **swap + unwrap on + BCH's DEX** (sell τUSDt, buy εUSDC, unwrap on Ethereum) — *not* by nested + bridge-of-a-bridge IOUs. Each token stays redeemable to its own home chain. + +--- + +## 3. Naming model (user-approved 2026-06-22, revises the earlier prefix stance) + +Three layers, kept separate: +1. **Identity = the CashToken category.** Tron-USDt and Ethereum-USDt are + unforgeably different tokens — different categories, can trade at different + prices. The category is the source of truth. +2. **Human name = the asset + a network badge.** Displayed ticker stays `USDt`; + the chain rides as BCMR metadata + a **mandatory network badge** (`USDt · Tron`). +3. **Greek prefix (ε/τ) = TEXT-ONLY fallback** for bare-string surfaces + (order-book rows, receipts, logs): Ethereum→ε, Tron→τ. Prefix leads the ticker + (a suffix doubles the *t* in `USDtτ`). + +**Banking mapping** (the user's framework): currency = ticker (`USDt`); +**BIC/bank-name = the chain badge** (which ledger); **IBAN/account = the +destination wallet address**; **@username/email/phone = an alias** (ENS, paymail) +resolving to the address. Banks surface the *destination* (currency + BIC), hide +the *mechanism*, and never put the *trust model* on the screen — so should we. + +We are **more honest than Circle**: Circle hides the per-chain distinction ("it's +all USDC") behind authority; we **surface** it (badge + category) because no one +guarantees `τUSDt = εUSDt` — they really are different claims. + +--- + +## 4. The conceptual spine (what the design discussion established) + +- **Lock-and-mint = debit/credit = correspondent banking, crypto-native.** A + cross-chain send is *debit here, credit there* — exactly a cross-bank wire + (nostro/vostro + SWIFT). The "wrap/IOU" words are back-office plumbing; the user + sees **Send.** +- **IOUs all the way down.** Bank deposits, USDt, USDc are all IOUs. "Native USDC" + is *Circle's* IOU; per-chain USDCs are *separate* IOUs unified only by Circle's + authority + CCTP. There is no pristine non-IOU thing. +- **Lock ≈ burn** (commutative — same result for the holder). The only reasons + they differ: (a) *eligibility* — burn needs issuer mint-authority (Circle can, + we can't burn Tether's USDt → we lock); (b) *backing verifiability*. +- **The only real axis is trustless vs authority** (not lock/burn, not + native/wrapped — those are implementation labels): + | Authority | Trust-minimized | Trustless | + |---|---|---| + | CCTP / USDC / WBTC / CEX | **our oracle lock-and-mint** | HTLC atomic swap / IBC / zk | + | trust an issuer/custodian, unbounded discretion (mint-at-will, freeze) | bounded auditable oracle + verifiable lock | no attestor — verify the source itself | + | = no on-chain check | = ASM·Multichain (today) | = DEX·Atomic (today) / future zk | +- **Verifiability beats promises.** Our advantage isn't "we promise honestly" — + it's *checkability*: `total τUSDt minted (BCH)` vs `total USDt locked (Tron)` is + publicly cross-checkable in real time. Over-minting is **detectable**. Circle's + reserves are off-chain, self-attested monthly — over-issuance leaves no trace. +- **USDm is the only stablecoin verifiable 1:1 to the bottom** (BCH-backed + on-chain, no bank, no authority). Wrapped `τUSDt` is hard-1:1 *at our bridge + layer*, but inherits Tether's unverifiable dollar-backing at the base — we add + **no new authority**, we just don't fix Tether's. + +--- + +## 5. Oracle vs light client vs zk — the verification spectrum + +The work always reduces to **two checks**: (1) **inclusion** — is the tx in a +block (Merkle proof); (2) **consensus** — is that block endorsed by the source's +validators/PoW. The *only* variable across architectures is **believe vs verify:** + +- **Oracle** = a courier whose handoff the destination **can't verify**, so it + **believes** the oracle's signature. `checkDataSig` proves *a key signed*, not + *that the claim is true*. Attack surface: the **key** (stealable) + the **input** + (poisonable). Has a secret. +- **Light client** = the destination **re-verifies the source's consensus + on-chain**. No key, no secret, **nothing to steal.** Trust reduces to "the + source chain's own consensus" — the irreducible floor. A relayer just carries + re-checkable evidence; a lying relayer is rejected. +- **zk proof** = a prover produces a proof the 2 checks were done correctly; + **soundness makes a false proof impossible.** Trust = math. + +> **An oracle is just a relayer the destination can't yet verify.** Give BCH the +> power to check the handoff (a zk verifier in a covenant), and the *same courier* +> drops from "trusted oracle" to "irrelevant relayer." The trust never lived in +> the messenger — it lives in the destination's inability to check. +> **"One `checkDataSig` away from IBC."** + +Recipient-swap note: a corrupt **oracle** *can* redirect a lock's mint (recipient +is an attested field). A **light client cannot** — the recipient is bound to the +real, consensus-confirmed source lock. This is a strong argument for the verifier. + +Boundary: this collapse holds for **cross-chain state** facts (independently +verifiable on the source). A pure **data oracle** (price/weather) has no +underlying proof → irreducibly trusted, never collapsible. + +--- + +## 6. Why BCH must use an oracle today (the VM wall) + +- BCH covenants can't run a Tron/Ethereum **light client**: no loops, tight + op/stack limits → can't verify ~27 Tron SR signatures + rotation per header; + **Ethereum impossible** (BLS aggregate sigs, no BLS opcode). +- **zk verification** needs **pairing opcodes BCH lacks** → a future **CHIP**. + **STARKs** (hash-based, no pairings) are the more plausible path but still + heavy; BCH's bigint + VM-limits upgrades move toward it but aren't there yet. +- **PoW chains** (BTC/LTC) are *more* verifiable (sha256 + difficulty), **but the + lock side is the wall**: Bitcoin has no covenants → can't release on a BCH event + → BTC↔BCH trustless = **HTLC atomic swap only** (a swap, needs a counterparty), + not a peg/mint. BTC single-party locks = timelock (`CLTV`) + hashlock (`HTLC`); + no foreign-event-conditional release. +- **The wall is always on the chain with the weaker script.** Tron/ETH→BCH: the + wall is BCH (verify). BTC/LTC→BCH: the wall is Bitcoin (lock). + +--- + +## 7. Security model + the reference hacks + +Our threat is **not** direct theft of the locked funds (non-custody holds — +locked asset is only releasable to the locker via refund, or the rightful claimant +via attested unwrap; **never** the operator). The threat is **minting unbacked +`τUSDt`** (via a stolen oracle key OR a covenant bug) and draining its value on the +DEX. Locked funds stay safe; IOU *supply integrity* depends on the oracle + the +covenant's correctness. + +| Hack | Vector | Assets / chains | Lesson for us | +|---|---|---|---| +| **Wormhole** $326M | **destination code bug** bypassed the guardian sig check → forged VAA → **over-minted 120k wETH** | wETH · Ethereum↔Solana | our covenant/`checkDataSig` correctness (why we robustness-test) | +| **Ronin** $625M | **stole 5-of-9 validator keys** → fraudulent withdrawals → drained reserve | ETH+USDC · Ethereum↔Ronin | oracle key security; M-of-N + bounded mint | +| **Multichain** ~$130M | **fake-MPC, CEO-held keys** (custodial) | many · many | structurally excluded (we have no custodian) | + +None were caused by the underlying chains' consensus — **all bridge-layer +failures** (code bug / stolen keys / custody). M-of-N hardens the *key* vector, +not the *code-bug* vector (Wormhole had 19 guardians — irrelevant when the check +itself was broken). + +**The trust ladder (our roadmap):** +`1 oracle (fragile) → M-of-N + independent sources per oracle + confirmation depth +(a staked/slashed, fee-earning network — Axelar/THORChain-class, buildable on BCH +today via multiple checkDataSig) → zk light-client (trustless, needs a pairing +CHIP)`. Staked-network ceiling: stake must scale with TVL; objective slashing for +a *false attestation* needs fraud-proofs. + +--- + +## 8. Build state (2026-06-21 → 06-23) + +**Build A — DONE, chipnet-validated.** `scripts/chipnet-amm-usdm-pusd.mjs` on +`SharePairAMM` (ADR-0030). USDm/PUSD **price discovery 1.000 → 1.440 PUSD/USDm**, +constant product held, LP withdraw. (v1 = stand-in FTs; v2 = real USDm category + +real Paryon PUSD.) Cauldron is *not* on chipnet and *not needed* — we have our own +AMM; `@cashlab/cauldron` is for mainnet compat later (ADR-0016). + +**Build B — cross-chain wrap, end-to-end validated across Tron Nile + chipnet.** +- `contracts/DeviantWrapMintIOU.cash` (cashc 0.12.1) — mint-on-attestation IOU + covenant: `checkDataSig` oracle gate, **no BCH backing** (dust only — value is + the source lock), **replay-bound to the reservoir outpoint**. Validated: + no-backing, reservoir decrement, **replay rejected by the covenant** (line 48 + outpoint binding — not the builder). +- `scripts/chipnet-wrapmint-iou.mjs` — BCH leg (genesis reservoir into the + covenant in one tx; oracle-attested mint). +- `contracts/tron/UsdtLockNile.sol` (+ `scripts/tron-deploy-lock.cjs`, evmVersion + istanbul) — Nile lock contract (lock/markConsumed/refund), **SafeERC20-fixed**, + deployed @ **`TVahBUBqUDLqVjDn77YavvqMvT2rtVnn2o`**. +- `scripts/tron-bch-wrap.mjs` — the **oracle watcher**: **real end-to-end wrap + validated** — locked **0.5 real USDt** on Nile → minted **500000 τUSDt** on + chipnet → `markConsumed`. Genuine `USDt(Tron) → τUSDt(BCH)`. + +**Robustness — found + fixed a real bug, then validated the safety paths.** +- **Bug:** `require(token.transfer(...))` assumed a standard `bool` return; real + Tether-style USDt returns nothing → **the refund path reverted → would strand + funds.** Fixed with **SafeERC20** (accept no-data or `true`), redeployed. +- `scripts/tron-wrap-robustness.cjs` — **R2 (too-early) ✓, R3 (consumed) ✓** (real + decoded reverts). **R1 (self-refund)** blocked by a **Nile-USDT token quirk** — + its non-standard `transfer` reverts for *contract* senders (EOA transfer works); + isolated with a clean token: `contracts/tron/MockUsdt.sol` + + `scripts/tron-refund-validate.cjs` → **R1 PASS**. +- Meta-lesson: the first test "passed" for the wrong reason (a TronWeb client error + counted as a contract revert). A guard only counts if it fails for the *right* + reason (`receipt.result === 'REVERT'`). + +**Deps:** `tronweb ^6.3.0`, `solc ^0.8.35`. Keys/state gitignored +(`.tron-*.json`, `.chipnet-*.json`, `ChipnetTests/`). + +**Commits:** `f32c28e` = Build A + Build B + Tron lock + docs + snapshot 0170. +**Uncommitted batch:** the naming-model docs + mockup source-chain dropdown + +SafeERC20 contract fix + MockUsdt + robustness/refund-validate scripts (a snapshot +0171 + commit is pending). + +**Mockup** (`design/mockups/bch-dex.html`, design-only, stays a demo): all four +cells; ASM·Multichain has the naming model (name + chain badge, prefix in +button/stepper), a **source-chain dropdown** (Ethereum/Tron → ε/τ), and +wrap/unwrap. Real swaps live in `shells/app/src/asm-core`. + +--- + +## 9. Deferred / next + +- **Unwrap (reverse leg):** burn `τUSDt` → oracle attests burn → `UsdtLockNile` + releases USDt on Tron. (Lock contract has the hooks; release path TBD. Keep the + non-custody invariant: release only to the rightful claimant, never the operator.) +- **Sepolia / εUSDC** (the second source chain). +- **M-of-N staked oracle network** (decentralize Build B; the real hardening). +- **zk / light-client verifier** via a future BCH CHIP → fully trustless; also + *binds recipient+amount to the verified source*, eliminating recipient-swap. +- **Real USDm + real Paryon PUSD** in the AMM (v2). +- **Send-abstraction UX:** one **Send** button that auto-routes cross-chain and + hides lock/mint/oracle entirely (bank-style: surface currency + ledger + fee + + time; never the mechanism or the trust model). + +## 10. Non-goals / honest caveats + +- **Universal redemption** (one IOU redeemable on any chain) = **NON-GOAL**: needs + pooled multi-chain reserves + rebalancing + higher trust, collapses the per-chain + markers, and **CCTP doesn't cover BCH**. The answer is **per-chain IOUs + the BCH + AMM swap** (`τUSDt → εUSDC` then unwrap) — emergent "redeem anywhere" given + liquidity, automatable. +- **Today = trust-minimized, not trustless.** The oracle is the residual trust — + bounded (mints exactly the attested amount/recipient) and auditable + (locked-vs-minted is public). The zk path removes it. +- We **add no new authority**: `τUSDt` is only as good as Tether's USDt + our + (non-custodial, verifiable) bridge. We don't become a second Circle. + +--- + +See also: `[[wrapped-asset-source-chain-marker]]`, +`[[exchange-is-a-function-not-p2p-venue]]`, `[[chipnet-dex-and-cross-chain-wrap-plan]]` +(auto-memory), ADR-0016/0030/0035, snapshot 0170. diff --git a/contracts/tron/MockUsdt.sol b/contracts/tron/MockUsdt.sol new file mode 100644 index 0000000..7d5a999 --- /dev/null +++ b/contracts/tron/MockUsdt.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.6; + +// MockUsdt — a minimal, well-behaved standard TRC-20 for testing the lock leg's +// refund path. Unlike the Nile faucet USDT (whose non-standard `transfer` reverts +// for contract senders), this token's transfer works from anyone — so it isolates +// the lock contract's own logic. Mints the full supply to the deployer. +contract MockUsdt { + string public name = "Mock USDT"; + string public symbol = "mUSDT"; + uint8 public decimals = 6; + uint256 public totalSupply; + mapping(address => uint256) public balanceOf; + mapping(address => mapping(address => uint256)) public allowance; + + event Transfer(address indexed from, address indexed to, uint256 value); + event Approval(address indexed owner, address indexed spender, uint256 value); + + constructor(uint256 supply) { + balanceOf[msg.sender] = supply; + totalSupply = supply; + emit Transfer(address(0), msg.sender, supply); + } + + function transfer(address to, uint256 v) external returns (bool) { + require(balanceOf[msg.sender] >= v, "balance"); + balanceOf[msg.sender] -= v; + balanceOf[to] += v; + emit Transfer(msg.sender, to, v); + return true; + } + + function approve(address s, uint256 v) external returns (bool) { + allowance[msg.sender][s] = v; + emit Approval(msg.sender, s, v); + return true; + } + + function transferFrom(address f, address to, uint256 v) external returns (bool) { + require(balanceOf[f] >= v, "balance"); + require(allowance[f][msg.sender] >= v, "allowance"); + allowance[f][msg.sender] -= v; + balanceOf[f] -= v; + balanceOf[to] += v; + emit Transfer(f, to, v); + return true; + } +} diff --git a/contracts/tron/UsdtLockNile.sol b/contracts/tron/UsdtLockNile.sol index 92b03ec..a599ae4 100644 --- a/contracts/tron/UsdtLockNile.sol +++ b/contracts/tron/UsdtLockNile.sol @@ -47,7 +47,7 @@ contract UsdtLockNile { require(amount > 0, "amount"); require(bchRecipient.length == 25, "bchRecipient"); // P2PKH locking bytecode require(deadline > block.timestamp, "deadline"); - require(token.transferFrom(msg.sender, address(this), amount), "transferFrom"); + _safeTransferFrom(msg.sender, address(this), amount); id = nextId++; locks[id] = Lock(msg.sender, amount, bchRecipient, deadline, false, false); emit Locked(id, msg.sender, amount, bchRecipient, deadline); @@ -70,7 +70,20 @@ contract UsdtLockNile { require(!L.settled && !L.consumed, "not refundable"); require(block.timestamp >= L.deadline, "too early"); L.settled = true; - require(token.transfer(L.locker, L.amount), "transfer"); + _safeTransfer(L.locker, L.amount); emit Refunded(id); } + + // SafeERC20: USDt (Tether-style) returns NO bool from transfer/transferFrom, so a + // strict `require(token.transfer(...), ...)` reverts and would strand funds. Accept + // success when the call doesn't revert AND (no return data OR it decodes to true). + function _safeTransfer(address to, uint256 amount) internal { + (bool ok, bytes memory data) = address(token).call(abi.encodeWithSelector(token.transfer.selector, to, amount)); + require(ok && (data.length == 0 || abi.decode(data, (bool))), "transfer failed"); + } + + function _safeTransferFrom(address from, address to, uint256 amount) internal { + (bool ok, bytes memory data) = address(token).call(abi.encodeWithSelector(token.transferFrom.selector, from, to, amount)); + require(ok && (data.length == 0 || abi.decode(data, (bool))), "transferFrom failed"); + } } diff --git a/design/mockups/bch-dex.html b/design/mockups/bch-dex.html index b560551..a577c2e 100644 --- a/design/mockups/bch-dex.html +++ b/design/mockups/bch-dex.html @@ -123,11 +123,13 @@ button:disabled{cursor:default}