Skip to content
Open
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: 2 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
EOA_DEPLOYER=...
ETHERSCAN_API_KEY=...
1 change: 1 addition & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dotenv_if_exists .env
32 changes: 32 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Compiler files
cache/
out/

# Ignores development broadcast logs
broadcast/
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/

# Docs
docs/

# Dotenv file
.env

node_modules
.env

# Hardhat files
/cache

# TypeChain files
/typechain
/typechain-types

# solidity-coverage files
/coverage
/coverage.json

# Hardhat Ignition default folder for deployments against a local node
ignition/deployments/chain-31337
8 changes: 8 additions & 0 deletions Tiltfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
local_resource(
"send-token-upgrade-deploy",
cmd = os.path.join(__file__, "..", "bin", "anvil-deploy"),
resource_deps = [
"anvil:base",
],
)

54 changes: 54 additions & 0 deletions bin/anvil-deploy
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env bun run
import "zx/globals";
$.verbose = true;

/**
* This script is used to deploy the Send token lockbox contract.
*/

const RPC_URL = "http://localhost:8546";
const baseSendMVPDeployer = "0x7F314BffCB437b7046F469dE2457f9C4014931e1";

const info = (msg: TemplateStringsArray, ..._: any[]) =>
console.log(chalk.blue(msg.join(" ")));

void (async function main() {
info`Enable auto-mining...`;
await $`cast rpc --rpc-url ${RPC_URL} evm_setAutomine true`;

info`Impersonating the Base Send MVP Deployer...`;
await $`cast rpc --rpc-url ${RPC_URL} \
anvil_impersonateAccount \
${baseSendMVPDeployer}`;

info`Funding the Base Send MVP Deployer...`;
await $`cast send --rpc-url ${RPC_URL} \
--unlocked \
--from 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 \
${baseSendMVPDeployer} \
--value 10ether`;

// remove previous deployments
const artifactsDir = path.resolve(
__dirname,
"..",
"ignition",
"deployments",
"chain-845337"
);
info`Removing previous deployments...`;
await $`rm -rf ${artifactsDir}`;

info`Deploying SendLockbox...`;
await $`echo yes | bunx hardhat ignition deploy --network anvil ./ignition/modules/SendToken.ts`;

info`Disable auto-mining...`;
await $`cast rpc --rpc-url ${RPC_URL} evm_setAutomine false`;

info`Re-enable interval mining... ${$.env.ANVIL_BLOCK_TIME ?? "2"}`;
await $`cast rpc --rpc-url ${RPC_URL} evm_setIntervalMining ${
$.env.ANVIL_BLOCK_TIME ?? "2"
}`; // mimics Tiltfile default

console.log(chalk.green("Done!"));
})();
2 changes: 2 additions & 0 deletions bunfig.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[install.scopes]
"0xsend" = { token = "$npm_token", url = "https://registry.npmjs.org/" }
9 changes: 9 additions & 0 deletions contracts/ISendLockbox.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.28;

interface ISendLockbox {
event Deposit(address indexed to, uint256 amount);

function deposit(uint256 amount) external;
function depositTo(address to, uint256 amount) external;
}
8 changes: 8 additions & 0 deletions contracts/ISendToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.28;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

interface ISendToken is IERC20 {
function mint(address to, uint256 amount) external;
}
51 changes: 51 additions & 0 deletions contracts/SendLockbox.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

import "./ISendToken.sol";
import "./ISendLockbox.sol";

contract SendLockbox is ISendLockbox {
using SafeERC20 for IERC20;

/// @notice The old ERC20 token of this contract
IERC20 public immutable SEND_V0;

/// @notice The new ERC20 token of this contract
ISendToken public immutable SEND_V1;

/// @param sendv0 The address of the old ERC20 contract
/// @param sendv1 The address of the new ERC20 contract
constructor(address sendv0, address sendv1) {
SEND_V0 = IERC20(sendv0);
SEND_V1 = ISendToken(sendv1);
}

/// @notice Deposit tokens into the lockbox and mints the new token to sender
/// @param amount The amount of tokens to deposit
function deposit(uint256 amount) external {
_deposit(msg.sender, amount);
}

/// @notice Deposit ERC20 tokens into the lockbox
/// @param to The user who should received minted tokens
/// @param amount The amount of tokens to deposit
function depositTo(address to, uint256 amount) external {
_deposit(to, amount);
}

/// @notice Deposit tokens into the lockbox
/// @param to The user who should received minted tokens
/// @param amount The amount of tokens to deposit
function _deposit(address to, uint256 amount) internal {
SEND_V0.safeTransferFrom(msg.sender, address(this), amount);
emit Deposit(to, amount);

/// @notice v0 token has 0 decimals, v1 token has 18 decimals, therefore we multiply by 1 ether
/// @notice v0 token has 100B supply, v1 token has 1B supply, therefore divided by 100
uint256 amountToMint = (amount * 1 ether) / 100;
SEND_V1.mint(to, amountToMint);
}
}
32 changes: 32 additions & 0 deletions contracts/SendToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";

/*

███████╗███████╗███╗ ██╗██████╗ ██╗████████╗
██╔════╝██╔════╝████╗ ██║██╔══██╗ ██║╚══██╔══╝
███████╗█████╗ ██╔██╗ ██║██║ ██║ ██║ ██║
╚════██║██╔══╝ ██║╚██╗██║██║ ██║ ██║ ██║
███████║███████╗██║ ╚████║██████╔╝ ██║ ██║
╚══════╝╚══════╝╚═╝ ╚═══╝╚═════╝ ╚═╝ ╚═╝

*/
contract SendToken is ERC20Burnable {
address public immutable lockbox;

constructor(
string memory _name,
string memory _symbol,
address _lockbox
) ERC20(_name, _symbol) {
require(_lockbox != address(0), "ZL");
lockbox = _lockbox;
}

function mint(address to, uint256 amount) external {
require(msg.sender == lockbox, "NL");
_mint(to, amount);
}
}
58 changes: 58 additions & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox-viem";
import dotenv from "dotenv";

dotenv.config();

const config: HardhatUserConfig = {
solidity: "0.8.28",
networks: {
// base mainnet network
hardhat: {
chainId: 8453,
forking: {
url: "https://mainnet.base.org",
},
},
anvil: {
url: "http://127.0.0.1:8546",
chainId: 845337,
},
sepolia: {
url: "https://sepolia.base.org",
chainId: 84532,
accounts: [process.env.EOA_DEPLOYER!],
},
base: {
url: "https://mainnet.base.org",
chainId: 8453,
accounts: [process.env.EOA_DEPLOYER!],
},
},
etherscan: {
apiKey: {
base: process.env.ETHERSCAN_API_KEY!,
sepolia: process.env.ETHERSCAN_API_KEY!,
},
customChains: [
{
network: "base",
chainId: 8453,
urls: {
apiURL: "https://api.basescan.org/api",
browserURL: "https://basescan.org",
},
},
{
network: "sepolia",
chainId: 84532,
urls: {
apiURL: "https://api-sepolia.basescan.org/api",
browserURL: "https://sepolia.basescan.org",
},
},
],
},
};

export default config;
34 changes: 34 additions & 0 deletions ignition/modules/SendToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// This setup uses Hardhat Ignition to manage smart contract deployments.
// Learn more about it at https://hardhat.org/ignition

import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";

const TOKEN_NAME = "Send";
const TOKEN_SYMBOL = "SEND";

/// @dev Lockbox address is pre-calculated
const LOCKBOX_ADDRESS = "0x60E5445EDc1A469CFc0181861c88BD4B6895F615";

/// @dev SendV0 address on Base chain
const SEND_V0 = "0x3f14920c99beb920afa163031c4e47a3e03b3e4a";

/// @dev Make sure we are using deployed that pre-calculated the lockbox address
export const EOA_DEPLOYER = "0x7F314BffCB437b7046F469dE2457f9C4014931e1";

const SendTokenModule = buildModule("SendTokenModule", (m) => {
const sendToken = m.contract(
"SendToken",
[TOKEN_NAME, TOKEN_SYMBOL, LOCKBOX_ADDRESS],
{
from: EOA_DEPLOYER,
}
);

const sendLockbox = m.contract("SendLockbox", [SEND_V0, sendToken], {
from: EOA_DEPLOYER,
});

return { sendToken, sendLockbox };
});

export default SendTokenModule;
28 changes: 28 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "@0xsend/send-token-upgrade",
"version": "0.0.3",
"devDependencies": {
"@nomicfoundation/hardhat-toolbox-viem": "^3.0.0",
"@types/bun": "latest",
"hardhat": "^2.22.17",
"@openzeppelin/contracts": "^5.1.0",
"dotenv": "^16.4.7",
"zx": "^8.3.0"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"scripts": {
"test": "hardhat test"
},
"files": [
"artifacts",
"ignition",
"contracts",
"bin",
"README.md",
"package.json",
"tsconfig.json",
"hardhat.config.ts"
]
}
Loading