Skip to main content
@sauce/token reads and moves tokens: ERC-20 on EVM chains, SPL Token and Token-2022 on Solana. The token methods (USDC.transfer(…), Token(token).approve(…), pot.USDC) compile to calls into it, so a token write has one implementation however it is spelled. The CLI and routes.compileSauceProgram make these functions available without imports. Pass the token as the first argument. You can also import the functions explicitly:
token-functions.sauce.ts
token-import.sauce.ts
Using a function that your target does not support produces a compile error. For example, transferChecked is available only on Solana. On EVM, importing it reports “transferChecked is not exported by module …”; using it without an import reports “transferChecked is called but never declared”. See library target selection.

On EVM chains

Reads

The reads are typed ERC-20 calls. A call to an address with no code returns no data, so the read reverts.

Writes

The writes have OpenZeppelin SafeERC20 semantics:
  • Empty return data is success only from an address with code. A token that returns nothing, such as USDT, works. A write to an address with no code reverts, so a mistyped address cannot pass silently.
  • A non-empty reply must contain at least 32 bytes, with the first word equal to 1. A false return, a shorter reply or a first word other than 1 fails the check.
  • approve is forceApprove. It reads the Pot’s current allowance for the spender, and when that is not zero, sets it to zero before setting the new amount. USDT refuses to change one non-zero allowance to another, so an approve works only from zero.
  • Writes return nothing. A write either succeeds or raises a failure. An unhandled failure reverts the cook; EVM programs can use the documented catch boundaries to handle one.
  • transferFrom spends an allowance. from must have approved the Pot for at least amount.
Every token write the package offers has these semantics. The one unchecked spelling is a raw IERC20.at(token).transfer(…) binding that a program writes itself, which decodes the declared bool: it reverts on USDT and accepts false. See Contracts and ABIs.

Signatures: permit and permitTransferFrom

permit sets an allowance on the holder’s signature and moves nothing; a later transferFrom pulls. permitTransferFrom pulls into the Pot in one step:
  • permit submits an EIP-2612 permit(owner, spender, value, deadline, v, r, s) and then requires the token’s nonce for owner to have moved by exactly one. A token with no permit but a permissive fallback, such as WETH9, would otherwise pass for one that granted the allowance. A transferFrom then spends the allowance. The token must have nonces(address), or the nonce read reverts.
  • permitTransferFrom calls Permit2’s signature transfer, moving amount from owner to the Pot on owner’s one-time signature. Permit2 spends nonce, rejects an expired deadline, and moves the tokens with the same empty-or-true check.
pull-permit2.sauce.ts
sauce run --fund signs a Permit2 or EIP-2612 permit with your wallet and adds these pulls to the program. See Funding.

On Solana

The Solana implementation supports both SPL Token and Token-2022. Its functions take the accounts they read or move through, because a Solana instruction lists every account it touches:

Reads

  • A token account must be owned by SPL Token, or by Token-2022 with the base account layout or an account-type byte of 2. A mint, a multisig or any other account is refused with not a token account, and an account of another mint with wrong mint.
  • totalSupply and decimals take the mint as an Account, not an Address. The account must be an initialized mint of either program, or the read fails with not a mint or uninitialized mint.
  • ownerOf returns the holder recorded in the token account, not the authority a write is signed by. SPL accepts the account’s delegate as the authority too, so a program that measures its own balance requires ownerOf(token, account) to equal the authority first.
  • allowance reads the account’s single delegate. It returns the delegated amount when the account has a delegate and the delegate is spender, and 0 otherwise. A revoked delegate reads as 0.

Writes

  • transfer, transferFrom and approve call SPL Token’s plain instructions. They require tokenProgram to be SPL Token (not the token program), source to be an SPL Token account (not a token account) of token (wrong mint), and amount to fit 64 bits (amount over u64). A Token-2022 callee or account is refused. transferFrom is transfer: SPL Token has one transfer instruction, and the authority you attach decides whether the owner or a delegate moves the funds.
  • transferChecked and approveChecked carry the mint and its decimals. They require the callee to be SPL Token or Token-2022, the source, destination (for a transfer) and mint to be owned by that program, mint to be the named token, and decimals to be the mint’s own (wrong decimals).
  • A Token-2022 mint whose transfer hook names a program is refused with transfer hook, before anything moves. The hook’s extra accounts cannot be carried in a fixed account list. A hook extension that names no program is no hook. An approval runs no hook, so approveChecked does not check for one.
  • The other Token-2022 extensions are the token program’s to apply. A transfer fee is withheld from what the destination receives, so measure the balance that arrived. A paused mint or a frozen account refuses the transfer with the token program’s own error, and amounts are always raw base units.
  • approve sets one delegate. SPL Token tracks a single delegate per account, so a second approve replaces the first, where an ERC-20 keeps an allowance per spender.
  • Amounts are 64-bit. An approval of 2^256 - 1, which an ERC-20 accepts, reverts with amount over u64.

The token methods choose the write

The token methods pick the write by the mint’s token program. An SPL Token mint (USDC, USDT, WSOL) moves with the plain transfer. A Token-2022 mint (PYUSD and the tokenized stocks) moves with transferChecked, using the registry’s decimals. Compiling adds each account the call moves through to main and derives it:
pay-pyusd.solana.sauce.ts
Called directly, the functions take accounts as Account parameters of main:
checked.solana.sauce.ts

Next steps

  • @sauce libraries: importing, and the EVM and Solana implementations.
  • @sauce/router: swaps at each venue, built on these writes.
  • Names: token symbols, pot.<TOKEN> and the token methods.
  • EVM and Solana: accounts, token programs and size limits on Solana.
  • token namespace: the TypeScript API that emits these calls for a host program.