Integration checklist for Coinbase tokenized stocks on Base
20 things to check before a contract holds, prices or settles a Coinbase stock token. Most of them do not come up with an ordinary ERC-20 and a crypto price feed, and we hit several of them the slow way while building a covered call vault on TSLAc.
The price feed
1.The feed stops when the US market is closed#
We recorded 13 Coinbase stock feeds over 100 days. All 182 weekends in that data had no round for at least 48 hours. An ordinary weekend ran 48.0 to 58.3 hours with a median of 52.2. Over Labor Day the gap was 74.5 to 81.6 hours. The feeds came back at Sunday 8 PM New York, within two minutes, on all 169 ordinary weekends.
Through all of it latestRoundData() keeps answering with Friday's price and Friday's updatedAt. Nothing reverts. Base's docs describe the same behaviour (24/5, value held on nights, weekends, holidays and corporate action pauses) and tell integrators to "never settle or liquidate against a frozen feed".
Check: every price read looks at updatedAt. For each function that uses a price, write down what it does when the price is old: revert, wait, or fall back. Settlement, liquidation and any swap at the oracle price should not run on a held value. Add a test that warps to a Saturday.
2.A staleness bound copied from a crypto feed fails on weekdays too#
These feeds publish on a 0.5% price move or a 24 hour heartbeat. A quiet stock can go most of a trading day without a round. Share of time the newest round was older than the bound, with the weekend gaps taken out:
| Bound | Across the 13 feeds | TSLA | AAPL |
|---|---|---|---|
| 1 hour | 22% to 79% | 60.9% | 78.9% |
| 4 hours | 1% to 49% | 23.3% | 48.8% |
| 12 hours | 0% to 13% | 2.0% | 12.7% |
| 24 hours | 0% | 0.0% | 0.0% |
The longest gap that was not a weekend ranged from 7.0 hours (MSTR) to 24.0 hours (GOOGL, MSFT).
Check: a bound a little above 24 hours keeps the trading week open. Anything shorter rejects live weekday prices. A staleness bound on its own also cannot tell a quiet Tuesday from a Saturday morning: with a 25 hour bound, a round from Friday 14:21 UTC still passes at Saturday noon. If that matters to you, add a market hours check and keep the staleness bound as the backstop. In our data the feeds published from Sunday 8 PM to Friday 8 PM New York and skipped the Labor Day holiday.
3.The last price before a closure is usually not the closing price#
On 111 of the 182 weekends, the last round before the weekend was published before the 4 PM New York close on Friday. The earliest was 13:41 UTC on a Friday, more than six hours before the close. Since a round needs a 0.5% move, the held value can be up to 0.5% away from the last trade even when it is recent.
The price on the first round after the weekend differed from the held one by a median of 48 bps. The largest we saw was 410 bps (INTC, weekend of 11 September 2026).
Check: if an option, loan or auction expires at the Friday close and settles on "the first round at or after expiry", it will usually settle on Sunday night's price, about 52 hours later. If you want Friday's price, put the expiry inside the trading day or settle on the last round before expiry. If you keep the first round after, say so in the product, because holders carry the weekend.
4.Two feeds can exist for the same stock#
For TSLA, NVDA and GOOGL there is a second Chainlink feed whose description() ends in (24/7). Their first rounds are from 4 and 5 November 2026 and they do print on weekends. On Saturday 14 November 2026 at 17:48 UTC:
| description() | Address | Price | updatedAt (UTC) |
|---|---|---|---|
| Coinbase TSLA | 0xFaf869185383a24F8cb00e27BdA6b63B9905DCb4 | 387.793 | Fri 13 Nov 21:32 |
| Coinbase TSLA (24/7) | 0xa5Ccd0315346b9A8A1D0AFEcD7Cc4e820e405e96 | 385.450 | Sat 14 Nov 16:38 |
Base's docs table lists the ten 24/5 feeds. We have not found the 24/7 feeds described there, so we do not know what they price on a weekend or how they are meant to be used.
Check: pin the feed by address and assert its description() in a test. Use the same feed for pricing and for settlement. Ask Base or Chainlink before you rely on a 24/7 feed.
5.The feed prices one token, and one token is not one share#
The Coinbase feeds report a total return value: the share price times the token's multiplier (see item 11). GOOGLc's multiplier is 1.000377 today, so the GOOGL feed sits 0.04% above the share price, and that difference grows with each dividend.
Check: token amount times feed price is the value. Do not apply the multiplier a second time. If you take a share price from a stock data API, multiply it by the multiplier to get the token price. A strike or limit quoted "per share" is a different number from one quoted per token.
6.A corporate action freezes the feed while the token keeps moving#
During a corporate action the feed's pause flag is set and its price goes stale, on a weekday, while transfers of the token are not paused. The feed resumes once both the price and the multiplier reflect the new values. So your contract can keep taking deposits, trades or liquidations against a frozen price in the middle of the week.
Check: the same guard as item 1 covers this, as long as it is a staleness bound and not only a weekend calendar. Run the Saturday test on a Wednesday timestamp as well.
7.Most stock tokens have no feed#
Coinbase's public token list returned 358 stock tokens on 14 November 2026. Twelve of them carried a NAV price. Base's docs list ten Chainlink feeds.
Check: keep an explicit list of tokens that have a feed and reject the rest. Do not fall back to a DEX spot price for a token without one. Base's docs note that the token's DEX price does not feed the oracle, and a thin pool is cheap to move.
8.Round ids are not a counter#
A Chainlink proxy numbers rounds as (phase << 64) | round. The TSLA feed moved from phase 1 to phase 2 in August 2026 and the two phases overlap in time. getRoundData for a round that does not exist inside an existing phase returned all zeros and did not revert. For a phase that does not exist it reverted with no data.
Check: treat updatedAt == 0 as "no round". Do not walk rounds with roundId - 1 across a phase change. If anyone can supply the round you settle on, verify it: the round before it is older than the expiry and this one is at or after it.
9.The sequencer check still applies#
This one is not specific to stocks, but it is easy to drop when the staleness logic gets rewritten. Chainlink's guidance for L2 feeds is to read the sequencer uptime feed and wait out a grace period after a restart. On Base that feed is 0xBCF85224fc0756B9Fa45aA7892530B47e10b6433 (its description() reads "L2 Sequencer Uptime Status Feed").
Check: the price path reads the uptime feed before it trusts a round.
The token
10.There is no bytecode at the token address#
The stock tokens are B20 precompiles inside the Base node. eth_getCode at TSLAc (0xb2000000000000000000001e800a7f5189430cD0) returns one byte, 0xef. There is no per token contract and no verified source on Basescan. On a stock Foundry fork that byte is all there is: with anvil 1.5.1 a call to the token failed with OpcodeNotFound, and inside forge test the call burned all the gas it was given.
Check: your fork tests really execute the token. Use Base's base-std mocks or base-anvil. To run against the real precompile without a key, send one eth_call with a state override that puts your test contract's code at an unused address. That worked for us on three public Base RPCs. Do not gate anything on the token's code size or code hash.
11.A raw balance is not a share count#
Dividends are reinvested and stock splits are applied through a multiplier. balanceOf, totalSupply, transfer amounts and allowances stay in raw token units and do not change. The number of shares a balance stands for is raw * uiMultiplier() / 1e18.
This is already live. On 14 November 2026, 9 of the 358 listed tokens had a multiplier other than 1, for example NVDAc at 1.00054, SANc at 1.0106 and PMc at 1.5588. GOOGLc's uiMultiplier() read 1000377118676784179 on chain.
Check: keep all accounting in raw token units and price them with the token's feed. Convert to shares only for display, with toUIAmount. Search the code and the front end for anything that assumes 1 token equals 1 share. Read the multiplier when you need it and do not cache it: a scheduled change takes effect at effectiveAt with no transaction and no event at that moment (UIMultiplierUpdated fires earlier, when the change is scheduled), and an instant update path also exists.
12.Eight decimals#
All 358 listed stock tokens report 8 decimals, and the feeds return 8 decimals too. The B20 standard allows 6 to 18, so a later token could differ.
Check: read decimals() and look for 1e18 constants in share and price maths. Check the rounding direction of every division, and test with a deposit of one raw unit.
13.The issuer can block an address, and yours is an address#
Each transfer is checked against a policy for the sender, the receiver and the caller. On TSLAc all three scopes point at policy id 5 in the policy registry (0x8453000000000000000000000000000000000002). If any of the three is not authorized the transfer reverts with PolicyForbids(scope, policyId). The list is edited in the registry by the policy's admin, so the token itself emits nothing when an address is added.
Check:
- One blocked recipient must not stop everyone else. Avoid loops that push tokens to many users. Let each user claim, or catch the failed transfer and hold that amount for them.
- A liquidation or settlement that pays a blocked address should park the payment, not revert.
approveis not gated by policy or by pause. A good allowance does not show that a transfer will go through.isAuthorized(policyId, account)never reverts, even for a policy id that does not exist. CallpolicyExistsfirst.- If your own contract is blocked, deposits and withdrawals both revert and no code path fixes it. Say that in your risk section.
14.A balance can move without your contract being called#
B20 has seizeWithMemo, which moves a balance from a holder to another address with no allowance and without the transfer policies. An account is seizable when the token's seize exempt policy does not authorize it. On TSLAc that scope is set (policy id 129). A fresh address and our own vault both read as exempt today.
Check: do not assume balanceOf(address(this)) is always at least your internal total. Decide who carries a shortfall. Share based accounting spreads it across holders. Fixed claims pay the first withdrawers in full and leave the last ones short.
15.Transfers can be paused on their own#
B20 pauses four features separately: transfer, mint, burn and seize. A paused transfer reverts with ContractPaused, while approve keeps working. Base's docs call an onchain pause unlikely. On TSLAc none of the four was paused when we read it.
Check: list every deadline in the contract (expiry, auction end, liquidation grace, withdrawal window) and ask what happens if transfers are paused across it. Test it with a mock that pauses.
16.Names, symbols and permit#
The issuer can change a token's name and symbol. A name change also changes the EIP-712 domain. permit accepts EOA signatures only, with no ERC-1271 support.
Check: identify tokens by address everywhere, the front end included. Keep a plain approve path, since smart accounts cannot use permit and a permit signed before a rename stops working.
17.How the real token answers at the edges#
From a 41 call probe of the real TSLAc on 13 November 2026 (one read only eth_call with a state override, nothing signed):
transfer,transferFromandapprovereturntrue. Zero amounts and transfers to yourself succeed.- A transfer to
address(0)or to the token's own address reverts withInvalidReceiver. - Failures are custom errors:
InsufficientBalance(sender, balance, needed),InsufficientAllowance(spender, allowance, needed). transferFromchecks the receiver, then the sender, then the allowance, then the balance.- An allowance of
type(uint256).maxis not spent down. - An unknown selector reverts with that selector as the revert data.
Check: anything that reads revert data (a try/catch, a router, a front end error map) expects these custom errors and not a string.
Chain and scope
18.Base blocks are 200 milliseconds#
3,000 blocks took 600 seconds when we read them on 14 November 2026. A duration written as a block count at 2 seconds a block is off by ten times.
Check: every epoch, cooldown, expiry and TWAP window uses block.timestamp. Fork tests that roll blocks to pass time use the same block time.
19.Who the tokens are for#
Base's docs state that Coinbase tokenized stocks are only available to persons in eligible jurisdictions outside the US. The Stocks Maxxing FAQ puts projects that enable trading for US users out of scope.
Check: the app says who it is for, and the front end does something about it. This is not legal advice. Ask someone who gives it.
20.Write the issuer's powers into your risk section#
The B20 standard has no built in timelock. The issuer's roles can change the multiplier, edit policy lists, pause features, seize, rename the token and change the supply cap, and each takes effect when the call lands. Announcement events are notice and they do not enforce a delay.
Check: your docs name these as assumptions users take on. A monitor watches UIMultiplierUpdated, Paused, Unpaused, PolicyUpdated, Seized and Announcement on the tokens you hold.
Tests worth having before you ship
- A price read on a Saturday does what you designed, and so does one during a weekday freeze.
- A 20 hour old price on a trading day is accepted.
- An expiry at the Friday close settles on the round you documented.
- A token with no feed, or a feed with the wrong
description(), is rejected. - One blocked recipient in a batch does not stop the others.
- Transfers paused across a deadline leave nobody worse off.
- A multiplier other than
1e18changes what the UI shows and leaves the accounting alone. - A deposit of one raw unit, and a withdrawal that leaves one raw unit.
- The fork tests fail if the token is not really being executed.
How the numbers were measured
Feed rounds were read from Base mainnet through public RPCs on 14 November 2026, at blocks 57436913 to 57437588. For each feed we read every round of the newest aggregator phase through the proxy: 28,887 rounds across 13 feeds, from 5 August to 14 November 2026. The feeds are the ten in Base's docs (AAPL, AMZN, GOOGL, META, MSFT, MSTR, NVDA, SNDK, SPCX, TSLA) plus Coinbase COIN, CRCL and INTC.
A weekend gap is the time between the last round before Saturday 12:00 UTC and the first round after it. There are 14 complete weekends per feed, 182 in total. The table is here: weekend-gaps.csv.
Token facts were read with eth_getCode and eth_call at block 57440547 (14 November 2026, 17:47 UTC). The token list is api.coinbase.com/v1/tokenized-stocks, read the same day. Base's documentation is Tokenized stocks on Base and the B20 standard.
Limits: this is a snapshot. Feed schedules, policies and multipliers can change, so read them again before you rely on a number. The timestamps show when rounds were published and say nothing about why. The token probe ran on TSLAc only. The list covers what is particular to these tokens and leaves out the general checks every contract needs.
If you want someone to read your code
A checklist catches the traps you already know about. A review is for the rest.
Kerne's kUSD contracts were audited by Hexens in July 2026. The report and our response to each of the 10 findings are public at kerne.fi/security/hexens-2026. Our stock vault has not been audited, and its page says so.
If your team wants an audit of what you are shipping, write to liam@kerne.fi or message @Kerne_Protocol on Telegram and we will introduce you to the people we worked with there. Scope, price and timing are between you and them.