Developer Docs
AppGitHub
  • Welcome!
  • Smart contracts
    • Core
      • Introduction
      • Deep Dive
      • Smart Contract Reference
        • PWN Hub
          • Tags
        • PWN Config
        • PWN Vault
        • Loan Types
          • Simple Loan
        • Proposals
          • Simple Loan Proposal
            • Simple Proposal
            • List Proposal
            • Elastic Proposal
            • Elastic Chainlink Proposal
            • Dutch Proposal
        • PWN Utilized Credit
        • PWN LOAN
        • PWN Revoked Nonce
        • Peripheral Contracts
          • Acceptor Controller
            • World ID
          • State Fingerprint Computer
            • UniV3
            • Chicken Bonds
          • Pool Adapter
            • Aave
            • Compound
            • ERC4626
        • Miscellaneous
          • PWN Fee Calculator
          • PWN Signature Checker
          • PWN Errors
          • PWN Periphery
          • Timelock
    • PWN DAO
      • Governance
        • Optimistic
        • Token
      • Tokens
        • PWN
        • stPWN
        • vePWN
          • Stake
          • Power
          • Metadata
      • Epoch Clock
      • Miscellaneous
        • Errors
        • EpochPowerLib
    • Tools
      • PWN Safe
        • Architecture
        • Security considerations
        • Smart Contract Reference
          • PWN Safe Factory
          • ATR Module
            • Tokenized Asset Manager
            • Recipient Permission Manager
          • Whitelist
          • ATR Guard
            • Operators context
      • Token Bundler
      • PWN Deployer
    • Libraries
      • MultiToken
    • Contract Addresses
  • More documentation
    • PWN Docs
    • FAQ
    • Audits
    • Using PWN without front-end
  • Deprecated
    • PWN Beta
      • Architecture
      • PWN
        • Off-chain signed offer
        • Offer types
      • PWN Vault
      • PWN LOAN
Powered by GitBook
On this page
  • 1. Summary
  • 2. Important links
  • 3. Contract Details
  • Features
  • Functions
  • Asset struct
  • Events
Edit on GitHub
  1. Smart contracts
  2. Libraries

MultiToken

PreviousLibrariesNextContract Addresses

Last updated 11 months ago

1. Summary

MultiToken is a solidity library unifying , & transfers by wrapping them into a MultiToken Asset struct.

MultiToken supports .

2. Important links

  • If you want to use the MultiToken library in your solidity project, you can install our NPM package.

  • Run: npm install @pwnfinance/multitoken

3. Contract Details

  • MultiToken.sol contract is written in Solidity version 0.8.0 but can be compiled with any Solidity compiler version 0.8.*

Features

The library defines a token asset as a struct of token identifiers. It wraps transfer, allowance and balance check calls of the following token standards:

Unifying the function calls used within the PWN context so we don't have to worry about handling these token standards individually.

Functions

transferAssetFrom

Overview

Function for transfer calls on various token interfaces.

This function takes two arguments:

  • addresssource - Source address

  • addressdest - Destination address

Implementation

function transferAssetFrom(Asset memory asset, address source, address dest) internal {
    _transferAssetFrom(asset, source, dest, false);
}
safeTransferAssetFrom

Overview

Function for safe transfer calls on various token interfaces.

This function takes two arguments:

  • addresssource - Source address

  • addressdest - Destination address

Implementation

function safeTransferAssetFrom(Asset memory asset, address source, address dest) internal {
    _transferAssetFrom(asset, source, dest, true);
}
getTransferAmount

Overview

Getter function to get the maximum amount of the supplied asset that can be transferred.

This function takes one argument:

Implementation

function getTransferAmount(Asset memory asset) internal pure returns (uint256) {
    if (asset.category == Category.ERC20)
        return asset.amount;
    else if (asset.category == Category.ERC1155 && asset.amount > 0)
        return asset.amount;
    else // Return 1 for ERC721, CryptoKitties and ERC1155 used as NFTs (amount = 0)
        return 1;
}
transferAssetFromCalldata

Overview

Function for transfer calls on various token interfaces that can also handle calldata.

This function takes two arguments:

  • addresssource - Account/address that should initiate the transfer

  • addressdest - Destination address

  • boolfromSender - Boolean defining if msg.sender is the same as source

Implementation

function transferAssetFromCalldata(Asset memory asset, address source, address dest, bool fromSender) pure internal returns (bytes memory) {
    return _transferAssetFromCalldata(asset, source, dest, fromSender, false);
}
safeTransferAssetFromCalldata

Overview

Function for safe transfer calls on various token interfaces that can also handle calldata.

This function takes two arguments:

  • addresssource - Account/address that should initiate the transfer

  • addressdest - Destination address

  • boolfromSender - Boolean defining if msg.sender is the same as source

Implementation

function safeTransferAssetFromCalldata(Asset memory asset, address source, address dest, bool fromSender) pure internal returns (bytes memory) {
    return _transferAssetFromCalldata(asset, source, dest, fromSender, true);
}
permit

Overview

This function takes four arguments:

  • addressowner - Address that signed the permit

  • addressspender - Address that is getting the approval to transfer the _asset

Implementation

function permit(Asset memory asset, address owner, address spender, bytes memory permitData) internal {
    if (asset.category == Category.ERC20) {

        // Parse deadline and permit signature parameters
        uint256 deadline;
        bytes32 r;
        bytes32 s;
        uint8 v;

        // Parsing signature parameters used from OpenZeppelins ECDSA library
        // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/83277ff916ac4f58fec072b8f28a252c1245c2f1/contracts/utils/cryptography/ECDSA.sol

        // Deadline (32 bytes) + standard signature data (65 bytes) -> 97 bytes
        if (permitData.length == 97) {
            assembly {
                deadline := mload(add(permitData, 0x20))
                r := mload(add(permitData, 0x40))
                s := mload(add(permitData, 0x60))
                v := byte(0, mload(add(permitData, 0x80)))
            }
        }
        // Deadline (32 bytes) + compact signature data (64 bytes) -> 96 bytes
        else if (permitData.length == 96) {
            bytes32 vs;

            assembly {
                deadline := mload(add(permitData, 0x20))
                r := mload(add(permitData, 0x40))
                vs := mload(add(permitData, 0x60))
            }

            s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
            v = uint8((uint256(vs) >> 255) + 27);
        } else {
            revert("MultiToken::Permit: Invalid permit length");
        }

        // Call permit with parsed parameters
        IERC20Permit(asset.assetAddress).permit(owner, spender, asset.amount, deadline, v, r, s);

    } else {
        // Currently supporting only ERC20 signed approvals via ERC2612
        revert("MultiToken::Permit: Unsupported category");
    }
}
balanceOf

Overview

Function for checking balances on various token interfaces.

This function takes two arguments:

  • addresstarget - Target address to be checked

Implementation

function balanceOf(Asset memory asset, address target) internal view returns (uint256) {
    if (asset.category == Category.ERC20) {
        return IERC20(asset.assetAddress).balanceOf(target);

    } else if (asset.category == Category.ERC721) {
        return IERC721(asset.assetAddress).ownerOf(asset.id) == target ? 1 : 0;

    } else if (asset.category == Category.ERC1155) {
        return IERC1155(asset.assetAddress).balanceOf(target, asset.id);

    } else if (asset.category == Category.CryptoKitties) {
        return ICryptoKitties(asset.assetAddress).ownerOf(asset.id) == target ? 1 : 0;

    } else {
        revert("MultiToken: Unsupported category");
    }
}
approveAsset

Overview

Function for approving calls on various token interfaces.

This function takes two arguments:

  • addresstarget - Target address to be checked

Implementation

function approveAsset(Asset memory asset, address target) internal {
    if (asset.category == Category.ERC20) {
        IERC20(asset.assetAddress).safeApprove(target, asset.amount);

    } else if (asset.category == Category.ERC721) {
        IERC721(asset.assetAddress).approve(target, asset.id);

    } else if (asset.category == Category.ERC1155) {
        IERC1155(asset.assetAddress).setApprovalForAll(target, true);

    } else if (asset.category == Category.CryptoKitties) {
        ICryptoKitties(asset.assetAddress).approve(target, asset.id);

    } else {
        revert("MultiToken: Unsupported category");
    }
}
isValid

Overview

A view function to check the ID and amount values are valid for the asset category in the provided asset.

This function takes one argument:

Implementation

function isValid(Asset memory asset) internal view returns (bool) {
    if (asset.category == Category.ERC20) {
        // Check format
        if (asset.id != 0)
            return false;

        // ERC20 has optional ERC165 implementation
        if (asset.assetAddress.supportsERC165()) {
            // If ERC20 implements ERC165, it has to return true for its interface id
            return asset.assetAddress.supportsERC165InterfaceUnchecked(ERC20_INTERFACE_ID);

        } else {
            // In case token doesn't implement ERC165, its safe to assume that provided category is correct,
            // because any other category have to implement ERC165.

            // Check that asset address is contract
            // Tip: asset address will return code length 0, if this code is called from the asset constructor
            return asset.assetAddress.code.length > 0;
        }

    } else if (asset.category == Category.ERC721) {
        // Check format
        if (asset.amount != 0)
            return false;

        // Check it's ERC721 via ERC165
        return asset.assetAddress.supportsInterface(ERC721_INTERFACE_ID);

    } else if (asset.category == Category.ERC1155) {
        // Check it's ERC1155 via ERC165
        return asset.assetAddress.supportsInterface(ERC1155_INTERFACE_ID);

    } else if (asset.category == Category.CryptoKitties) {
        // Check format
        if (asset.amount != 0)
            return false;

        // Check it's CryptoKitties via ERC165
        return asset.assetAddress.supportsInterface(CRYPTO_KITTIES_INTERFACE_ID);

    } else {
        revert("MultiToken: Unsupported category");
    }
}
isSameAs

Overview

A view function to compare two assets (ignoring their amounts). Returns true if the assets are the same.

This function takes two arguments:

Implementation

function isSameAs(Asset memory asset, Asset memory otherAsset) internal pure returns (bool) {
    return
        asset.category == otherAsset.category &&
        asset.assetAddress == otherAsset.assetAddress &&
        asset.id == otherAsset.id;
}

Asset struct

Each asset is defined by the Asset struct and has the following properties:

Type
Name
Comment

category

Corresponding asset category

address

assetAddress

Address of the token contract defining the asset

uint256

id

TokenID of an NFT or 0

uint256

amount

Amount of fungible tokens or 0 -> 1

Events

The MultiToken library does not define any events or custom errors.

Asset memoryasset - struct defining all necessary context of a token

Asset memoryasset - struct defining all necessary context of a token

Asset memoryasset - struct defining all necessary context of a token

Asset memoryasset - struct defining all necessary context of a token

Asset memoryasset - struct defining all necessary context of a token

Wrapper function to allow grating approval using permit signature for ERC-20 (see ).

Asset memoryasset - struct defining all necessary context of a token

bytes memorypermitData - The Permit data itself. The data must include the permit deadline (uint256) and permit signature. The signature can be standard (65 bytes) or compact (64 bytes) defined in . Lastly, the deadline and signature should be pack encoded together.

Asset memoryasset - struct defining all necessary context of a token

Asset memoryasset - struct defining all necessary context of a token

Asset memoryasset - struct defining all necessary context of a token

Asset memoryasset - struct defining the first token to compare

Asset memoryotherAsset - struct defining the second token to compare

ERC20
ERC721
ERC1155
EIP-2612
EIP-2098
Asset
Asset
Asset
Asset
Asset
Asset
Asset
Asset
Asset
Asset
Asset
ERC20
ERC721
ERC1155
CryptoKitties
NPM package
GitHub - PWNFinance/MultiToken: Solidity library unifying ERC20, ERC721 & ERC1155 transfers by wrapping them into MultiToken Asset struct.GitHub
GitHub
Logo