Getting started
The short steps for a friend, a sender, a judge and a developer.
Pick the part that fits you. Each one stands on its own.
For a friend
Someone sent you a link to a gift. You do not need a wallet, a seed phrase or any crypto.
- Open the whole link. It looks like
https://<site>/g/<number>#<key>. If a chat app cut it short, the page says "This link doesn't open a gift" and the sender should copy it again, whole. - Tap "Sign in to open it" and sign in with Google or email. Moi makes a wallet for you on BNB Chain. There is no seed phrase and no fee. You can export the wallet's key later.
- Tick the box that says you are not a US person and not in the US or a restricted region. The "Open your gift" button stays off until you do.
- Tap "Open your gift". Moi moves the share into your wallet and pays the network fee. BNB Chain usually confirms in a few seconds.
- You see "It's yours", the number of shares, the sender's note, and a link to the transaction on BscScan.
If the gift has already been opened, taken back or has expired, the page says so. You pay nothing at any point.
For a sender
You need a browser wallet such as MetaMask or the Binance Web3 Wallet, set to BNB Smart Chain. Hold USDT in it to pay for the share, and a little BNB for network fees. In the live runs a swap used 358,378 gas and locking a gift used 348,697 gas.
- Open the send page and connect your wallet.
- Pick one of the nine stocks and an amount from 1 to 100 US dollars. A stock whose market is closed is dimmed and cannot be picked: the FAQ says exactly what the page shows. Write a note if you like, up to 483 bytes of text. It is sealed, so only the link can open it.
- Choose how long the gift stays open: 7, 30 or 90 days. The default is 30 days. The agent accepts any whole number of days from 1 to 90.
- Sign what your wallet asks, one step at a time:
- If it has not been done, allow the trading router to spend exactly your USDT amount.
- Sign the swap that buys the share.
- Allow the vault to take exactly the stock that arrived.
- Sign
createGift, which locks the share in the vault. - Sign the 5-cent gift wrap payment. It is paid in U, USD1, USDT or USDC. If you pay in USDT or USDC, your wallet may first ask you to approve exactly the fee for Permit2.
- The page shows your link as soon as the gift is locked, before the wrap payment. Copy it.
Keep the link private. Anyone who holds the whole link can open the gift, like cash in an envelope. Send it by a message or an email to the one person you mean, and do not post it. The key never reaches Moi's servers, so Moi cannot send you the link again. If the friend never opens it, you can take the gift back after it expires.
If you would rather send from the command line, use the sender agent.
For a judge
The judges section on the home page gives each judge one real stock gift of about one US dollar. The gifts were made from Moi's own wallet before judging, and Moi's relayer delivers them with no wrapping fee.
- Open the judges section on the home page.
- Press "Sign in with Google or email" (step 1) and sign in. If you use email, Privy sends a code to your inbox and you type it in. Moi makes a wallet for you, and step 1 then shows "Signed in as" with your email.
- Type the judge code from our submission into the box.
- Tick the declaration that you are not a US person and not in a restricted place.
- Press "Claim my share". Your wallet signs a short message that proves the wallet is yours, and Moi sends the request. The gift lands in your new wallet, and Moi pays the network fee.
Each judge gets one gift. Moi allows one gift per sign-in for 30 days, one gift per network a day, and at most four judge gifts in any hour. More than five wrong codes in a minute from one network are refused for that minute. If every judge gift has been handed out, the page says "All the judge gifts have been handed out. Ask the Moi team for another."
To check the result, look at the transaction on BscScan. You will see the vault's GiftClaimed event for a gift number and your new wallet as the recipient.
For a developer
What you need
- Node 22 or newer, and npm. The counts below were run on Node 26.7.0 and npm 11.19.0.
- Foundry for the contracts. Install it with
curl -L https://foundry.paradigm.xyz | bashand thenfoundryup; getfoundry.sh has the other ways. The repo pins no Foundry version, andfoundry.tomlpins only the Solidity compiler, 0.8.28. The counts below were run on forge 1.8.1.install-deps.shinstalls OpenZeppelin v5.7.0 and forge-std v1.17.0.
Clone and install
git clone https://github.com/ramakrishnanhulk20/Moi.git
cd Moi
npm installRun the tests
npm test
cd packages/contracts && sh install-deps.sh && forge testCounts on 8 October 2026:
| Suite | Result |
|---|---|
packages/core | 462 passed, 3 skipped |
packages/agent | 59 passed |
packages/contracts (forge) | 127 passed in 8 suites, 0 failed |
npm test type-checks first and then runs the core and agent tests. The 3 skipped core tests are fork tests. Turn them on by setting MOI_FORK_TESTS=1. Then npm run test:fork --workspace=@moi/core runs the claim fork test, and npx vitest run test/prove.fork.test.ts from packages/core runs the prove fork test. Both need Foundry's anvil and network access. The forge run includes 10 fork tests and 19 attack tests that read BNB Chain mainnet through the nodes named in foundry.toml, so it needs a network connection. The forge fuzz tests run 10,000 cases each.
Prove it against the live network
npm run proveIf you want proof with no setup at all, use the three cast reads in Prove it on the contracts page. They need Foundry and no keys. The prove script needs more.
Both modes read the whole .env first and stop with a named error unless OC_API_KEY, OC_SECRET_KEY, DEPLOYER_PRIVATE_KEY and RELAYER_PRIVATE_KEY are all present and well formed, even in the dry run. MOI_VAULT_ADDRESS is optional in the dry run and required in the live run.
The dry run, npm run prove, reads the chain, asks Binance for a quote and a swap for 1 USDT of NVDAB for the deployer wallet, lists the tokens the vault accepts and prints the plan. The script checks no balance, signs nothing and sends nothing, and it ends with Dry run complete. Nothing was signed or sent.
The live run, MOI_LIVE=1 npm run prove, spends 1 USDT on mainnet. The deployer wallet signs and sends the USDT approval for the trading router (only if its allowance is short), the swap into NVDAB, the vault approval and createGift. The relayer wallet then sends the claim into a brand-new wallet. So the deployer wallet needs at least 1 USDT and some BNB for gas, and the relayer wallet needs some BNB. The run prints PROVED: bought, gifted and claimed into a brand-new wallet through the relayer. only after it has checked every step on chain.
Before it sends anything, the live run refuses to start unless the vault lists NVDAB, is not paused and names the address of RELAYER_PRIVATE_KEY as its relayer. So it cannot claim through Moi's own relayer. Run it against a vault you deployed yourself with packages/contracts/deploy.sh. Also set MOI_SPONSOR_ADDRESS to the deployer wallet's address. The run's gift is not wrapped, and the claim rule delivers an unwrapped gift only from the sponsor wallet. Without that setting the claim is refused after the gift is locked, and the gift waits in the vault until it expires and refund returns it.
npm run slice is a smaller dry run: a live quote, the checks and the simulations, with nothing signed. It needs the same four keys.
Run the attacks
cd packages/contracts && forge test --match-path "test/attacks/*" --fork-url https://bsc-dataseed.bnbchain.org -vvv
npm run attacks --workspace=@moi/coreThe first command attacks the real vault on a fork of mainnet: 19 attacks. The second attacks the server code in process against a fresh vault on an anvil fork: 41 attacks. Foundry's anvil and a built packages/contracts/out must exist, and the run forks from Blast's public archive node. Set MOI_ATTACK_FORK_URL to use another archive node. Both runs refuse every attack. The security page explains what they cover.
Environment variables
Copy .env.example to .env at the repo root and fill it in. Never commit it. The table gives names only.
| Name | What it is for | Where you get it |
|---|---|---|
OC_API_KEY, OC_SECRET_KEY | Your credentials for the Binance Web3 API. Moi signs every request to Binance with them. | The Binance Web3 API developer portal, at web3.binance.com/en/dev-portal |
DEPLOYER_PRIVATE_KEY | Deploys the vault and acts as the test sender in scripts. Fund it with a little BNB and USDT on BNB Smart Chain. | A new wallet you make for this |
RELAYER_PRIVATE_KEY | Sends claims and pays their gas. It holds nothing else. Fund it with a little BNB. | A second new wallet |
ETHERSCAN_API_KEY | Used only by packages/contracts/verify.sh, which stops at the start if it is empty. Nothing else reads it. | A key from etherscan.io/myapikey. The free plan did not cover BNB Chain when checked on 7 October 2026, so the BscScan step needs a paid plan. The Sourcify step needs no key, and the live vault's source is verified there |
NEXT_PUBLIC_PRIVY_APP_ID | The Privy app that signs the friend in. It is public, not a secret, and exactly 25 characters. | An app created at dashboard.privy.io |
MOI_RELAYER_ADDRESS | The public address of the relayer wallet. The deploy script reads it so it never needs the relayer key. | The address of the relayer wallet |
MOI_OWNER_ADDRESS | The wallet that will own the vault. The deploy offers ownership to it, and it accepts with one signature. Required for a mainnet deploy. | The address of a wallet you control |
MOI_VAULT_ADDRESS | The deployed vault. The live one is 0x808EB6B3dC1ad50Ca114F5eA9d053BEAd958975C. | The output of the deploy |
MOI_RELAYER_MAX_GAS_PRICE_WEI, MOI_RELAYER_DAILY_CAP_WEI | Optional relayer limits. The defaults are 3 gwei and 0.002 BNB a day. | You choose them |
UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN | The shared store that remembers claimed gifts, locks and judge handouts. Set both or neither. | A free Upstash Redis database, in Singapore or Frankfurt |
MOI_WRAP_PRICE_USD | The gift wrap fee. The default is 0.05, and the most allowed is 1 dollar. | You choose it |
MOI_PAYOUT_ADDRESS | The wallet the wrap fees settle to. | The payout wallet you set in your b402 application |
MOI_SPONSOR_ADDRESS | The wallet that makes the judge gifts. The relayer delivers its gifts with no wrapping fee. | A wallet you control |
MOI_PUBLIC_ORIGIN | The site address printed in gift links. Use https in production. The example is http://localhost:3000. | Your site address |
MOI_SERVER_ORIGIN | Where the sender agent reaches Moi's server to wrap gifts. | Your site address |
MOI_JUDGE_SEED | A secret 32-byte seed, written as 0x and 64 hex characters, that the server derives each judge gift's claim key from. | Any secure random generator. Keep it secret |
MOI_JUDGE_POOL | The judge gifts the server may hand out, as giftId:index pairs. | Printed by npm run judge-gifts --workspace=@moi/core |
MOI_JUDGE_COUNT, MOI_JUDGE_STOCK, MOI_JUDGE_USD_EACH | How many judge gifts to make, which stock token, and how many dollars each. The defaults are 10, NVDAB and 1. | You choose them |
MOI_JUDGE_START | Resumes a stopped judge-gift run from this key index. The default is 0. | You choose it |
MOI_JUDGE_CODE | The code judges type on the judge button. | You choose it. The server checks its format |
BSC_RPC_URL | Optional. The BNB Smart Chain node to use. It must be https. The default is https://bsc-dataseed.bnbchain.org. | Any BNB Chain node provider |
MOI_ALLOW_MEMORY_STORE, MOI_DEV_COUNTRY, MOI_DEV_ALLOW_UNKNOWN_COUNTRY, PORT | Local development only. Set MOI_ALLOW_MEMORY_STORE=1 to run without Upstash in a single process. MOI_DEV_COUNTRY is the country a local request claims. PORT is for npm run serve. | You choose them |
Three switches change what a script does: MOI_LIVE=1 for prove and slice, MOI_FORK_TESTS=1 for the fork tests, and MOI_ATTACK_FORK_URL for the attack run.
To run the website on your machine, use npm run dev --workspace=@moi/web. It serves on http://127.0.0.1:3000.