Skip to content
This repository was archived by the owner on Jan 15, 2025. It is now read-only.

Commit 67fecc5

Browse files
committed
initial commit
1 parent ea68ff9 commit 67fecc5

File tree

14 files changed

+1102
-138
lines changed

14 files changed

+1102
-138
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ forta.config.json
99
**/.ipynb_checkpoints
1010
**/__pycache__
1111
**/artifacts/
12+
publish.log

LICENSE

Lines changed: 661 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,77 @@
1-
# High Gas Agent
1+
# NFT Sleep Minting Agent
22

33
## Description
44

5-
This agent detects transactions with high gas consumption
5+
A Forta Agent to detect transactions that may indicate NFT Sleep Minting.
6+
7+
Sleep Minting is when an attacker mints an NFT directly to a famous creator's wallet with permissions to reclaim or pull the NFT back out of the creator's wallet. This creates the appearance that (1) a famous creator minted an NFT to themselves, and (2) the creator sent that NFT to an attacker. Based on “on-chain” provenance, the attacker can claim they own an NFT created by a famous artist and sell it for a high value.
8+
9+
You can read more about what this is and why it matters [here](https://a16z.com/2022/03/09/sleep-minting-nfts/)
10+
11+
Documentation for how to build your own Forta Agent can be found [here](https://docs.forta.network/en/latest/)
12+
13+
You can see this agent live [here](https://explorer.forta.network/agent/0x20d0cd9432c7e15cb625097a718c15cc07f463b5252e3c36ae23acb7ef98d54e)
14+
15+
## Install
16+
17+
- `git clone https://github.com/a16z/nft-sleep-mint-forta-agent.git`
18+
- `cd nft-sleep-mint-forta-agent`
19+
- `npm install .`
620

721
## Supported Chains
822

923
- Ethereum
10-
- List any other chains this agent can support e.g. BSC
1124

1225
## Alerts
1326

14-
Describe each of the type of alerts fired by this agent
15-
16-
- FORTA-1
17-
- Fired when a transaction consumes more gas than 1,000,000 gas
18-
- Severity is always set to "medium" (mention any conditions where it could be something else)
19-
- Type is always set to "suspicious" (mention any conditions where it could be something else)
20-
- Mention any other type of metadata fields included with this alert
27+
- SLEEPMINT-1
28+
- Fired when an NFT is transferred by an address that is not the owner of the NFT according to an NFT Transfer event.
29+
- i.e., the transaction sender != the `from` argument in an emitted Transfer() event
30+
- Severity is always set to "info"
31+
- This type of transfer may be OK. There are cases where an address is approved to transfer an NFT on another person's behalf.
32+
- Type is always set to "suspicious"
33+
- SLEEPMINT-2
34+
- Fired when there is an NFT approval where the address that sent the approval transaction is different from the current NFT owner. This would be malicious if someone mints an NFT to a famous artist but maintains the permissions to pull that NFT back out of the famous artist's wallet.
35+
- Severity is always set to "medium."
36+
- Type is always set to "suspicious."
2137

2238
## Test Data
2339

24-
The agent behaviour can be verified with the following transactions:
40+
Create a file named `forta.config.json` in the root of the project directory that contains your own RPC endpoint from [Alchemy](https://www.alchemy.com/). See the example below. Note, use an appropriate endpoint for the chain you are looking to test. For example, use a Rinkeby endpoint to test a Rinkeby transaction hash or a Mainnet endpoint to test a Mainnet transaction hash.
41+
42+
```
43+
{
44+
"jsonRpcUrl": YOUR_RPC_ENDPOINT_HERE
45+
}
46+
```
47+
48+
The agent behavior can be verified with the following transactions:
49+
50+
- SLEEPMINT-1 (Mainnet): 0x57f23fde8e4221174cfb1baf68a87858167fec228d9b32952532e40c367ef04e
51+
- SLEEPMINT-1 (Rinkeby): 0x3fdd4435c13672803490eb424ca93094b461ae754bd152714d5b5f58381ccd4b
52+
- SLEEPMINT-2 (Rinkeby): 0x53aa1bd7fa298fa1b96eeed2a4664db8934e27cd28ac0001a5bf5fa3b30c6360
53+
54+
To test a transaction hash run the command: `npm run tx <TRANSACTION HASH>`. For example,
55+
56+
```
57+
npm run tx 0x57f23fde8e4221174cfb1baf68a87858167fec228d9b32952532e40c367ef04e
58+
```
59+
60+
## Real Example Sleep Mint
61+
62+
Here is the smart contract that sleep minted Beeple's "First 5000 Days" NFT: https://etherscan.io/address/0x5fbbacf00ef20193a301a5ba20acf04765fb6dac
63+
64+
## Testnet Example Sleep Mint
65+
66+
I deployed a simple [smart contract](https://rinkeby.etherscan.io/address/0x43A87F4a18db7b342C9FeC664D57cd30efDa5d3C#writeContract) on Rinkeby that allows you to sleep mint an NFT!
67+
68+
On the "write contract" etherscan page, use the `7. sleepMint` function to mint an NFT to another person's wallet (specify an address and a tokenId). Then use the `2. reclaimSleepMintedNFT` to reclaim or pull that NFT back out of that person's wallet (specify the tokenId you minted).
69+
70+
Here is an example [mint transaction](https://rinkeby.etherscan.io/tx/0x3a187049c3089fd67b88b4e62b3b78ea139f65e5c1684867d3b5870f115ff9d2) to another person's wallet and then a [reclaim transaction](https://rinkeby.etherscan.io/tx/0x35ac88018dac46ecf0e017137507663c21de98a06b8eb6a9cb463f219c0de14e).
71+
72+
73+
When you look at the NFT's [testnet OpenSea page](https://testnets.opensea.io/assets/0x43a87f4a18db7b342c9fec664d57cd30efda5d3c/1), and the Tokens transferred section on etherscan above, you can see how the Transfer event logs manipulate the NFT's on-chain provenance and show that `0xd2aff6...` minted and transferred the NFT even though `0xd2aff6...` was never involved with minting or transferring the NFT themselves.
74+
75+
## Disclaimer
2576

26-
- 0x1b71dcc24657989f920d627c7768f545d70fcb861c9a05824f7f5d056968aeee (1,094,700 gas)
27-
- 0x8df0579bf65e859f87c45b485b8f1879c56bc818043c3a0d6870c410b5013266 (2,348,226 gas)
77+
_This agent code is being provided as is. No guarantee, representation or warranty is being made, express or implied, as to the safety or correctness of the agent code. The agent code has not been audited and as such there can be no assurance it will work as intended, and users may experience delays, failures, errors, omissions or loss of transmitted information. THE AGENT CODE CONTAINED HEREIN ARE FURNISHED AS IS, WHERE IS, WITH ALL FAULTS AND WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING ANY WARRANTY OF MERCHANTABILITY, NON- INFRINGEMENT OR FITNESS FOR ANY PARTICULAR PURPOSE. Further, use of any of this agent code may be restricted or prohibited under applicable law, including securities laws, and it is therefore strongly advised for you to contact a reputable attorney in any jurisdiction where this agent code may be accessible for any questions or concerns with respect thereto. Further, no information provided in this repo should be construed as investment advice or legal advice for any particular facts or circumstances, and is not meant to replace competent counsel. a16z is not liable for any use of the foregoing, and users should proceed with caution and use at their own risk. See a16z.com/disclosures for more info._

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "forta-agent-starter",
33
"version": "0.0.1",
4-
"description": "Forta Agent Typescript starter project",
4+
"description": "Forta Agent for NFT Sleep Minting",
55
"scripts": {
66
"build": "tsc",
77
"start": "npm run start:dev",

src/ERC721_ABI.json

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/agent.spec.ts

Lines changed: 0 additions & 48 deletions
This file was deleted.

src/agent.ts

Lines changed: 16 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,23 @@
1-
import {
2-
BlockEvent,
3-
Finding,
4-
HandleBlock,
5-
HandleTransaction,
6-
TransactionEvent,
7-
FindingSeverity,
8-
FindingType,
9-
getEthersProvider
10-
} from 'forta-agent'
1+
import { Finding, HandleTransaction, TransactionEvent } from "forta-agent";
112

12-
import { BigNumber } from 'bignumber.js'
13-
import { ethers } from 'ethers'
14-
import ERC721_ABI from "./ERC721_ABI.json";
3+
import transferMismatch from "./transfer.mismatch";
4+
import approveMismatch from "./approve.mismatch";
155

16-
const ERC721_INTERFACE_ID = 0x5b5e139f
17-
const transferEvent = "event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)";
18-
const approveEvent = ""
19-
const mintEvent = ""
6+
const handleTransaction: HandleTransaction = async (
7+
txEvent: TransactionEvent
8+
) => {
9+
let findings: Finding[] = [];
2010

21-
const handleTransaction: HandleTransaction = async (txEvent: TransactionEvent) => {
22-
const findings: Finding[] = []
23-
let contract: ethers.Contract;
24-
const to: string = txEvent.to as string
11+
findings = (
12+
await Promise.all([
13+
transferMismatch.handleTransaction(txEvent),
14+
approveMismatch.handleTransaction(txEvent),
15+
])
16+
).flat();
2517

26-
try {
27-
28-
contract = new ethers.Contract(to, ERC721_ABI, getEthersProvider())
29-
let isNFT = await contract.supportsInterface(ERC721_INTERFACE_ID)
30-
if (!isNFT){
31-
throw 'not an ERC721 contract!'
32-
}
33-
34-
} catch (err){
35-
console.log(err)
36-
return findings
37-
}
38-
39-
const fromAddress = txEvent.from
40-
const transfers = txEvent.filterLog(transferEvent, contract.address);
41-
42-
for (let transfer of transfers){
43-
const transferFromAddress = transfer.args.from
44-
45-
if (transferFromAddress != fromAddress){
46-
findings.push(Finding.fromObject({
47-
name: "Sleeping Minted an NFT",
48-
description: `An NFT Transfer was initiated by ${fromAddress} to transfer an NFT owned by ${transferFromAddress}`,
49-
alertId: "SLEEPMINT-1",
50-
severity: FindingSeverity.Unknown,
51-
type: FindingType.Suspicious
52-
}))
53-
}
54-
}
55-
56-
return findings
57-
}
58-
59-
60-
// const handleBlock: HandleBlock = async (blockEvent: BlockEvent) => {
61-
// const findings: Finding[] = [];
62-
// // detect some block condition
63-
// return findings;
64-
// }
18+
return findings;
19+
};
6520

6621
export default {
6722
handleTransaction,
68-
// handleBlock
69-
}
23+
};

src/approve.mismatch.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import {
2+
Finding,
3+
HandleTransaction,
4+
TransactionEvent,
5+
FindingSeverity,
6+
FindingType,
7+
} from "forta-agent";
8+
9+
import { APPROVE_EVENT, APPROVAL_FOR_ALL_EVENT } from "./constants";
10+
11+
const handleTransaction: HandleTransaction = async (
12+
txEvent: TransactionEvent
13+
) => {
14+
const findings: Finding[] = [];
15+
16+
const contractAddress: string = txEvent.to as string;
17+
const txnSender = txEvent.from.toLowerCase();
18+
19+
// get all Approval and ApprovalForAll event logs
20+
let approvals = txEvent.filterLog(
21+
[APPROVE_EVENT, APPROVAL_FOR_ALL_EVENT],
22+
contractAddress
23+
);
24+
25+
for (let approve of approvals) {
26+
// get the current owner of an NFT
27+
const eventCurrentNFTOwner = approve.args.owner.toLowerCase();
28+
29+
// get the address approved to transfer the NFT
30+
const eventApprovedAddress =
31+
typeof approve.args.approved == "boolean"
32+
? approve.args.operator.toLowerCase()
33+
: approve.args.approved.toLowerCase();
34+
35+
// check if the txn sender is not the current NFT owner
36+
// check if the txn sender is approving themselves to transfer another person's NFT
37+
if (
38+
eventCurrentNFTOwner != txnSender &&
39+
eventApprovedAddress == txnSender
40+
) {
41+
findings.push(
42+
Finding.fromObject({
43+
name: "Sleep Minted an NFT",
44+
description: `An NFT was approved for ${txnSender}, by ${txnSender}, but owned by ${eventCurrentNFTOwner}. The NFT contract address is ${contractAddress}`,
45+
alertId: "SLEEPMINT-2",
46+
severity: FindingSeverity.Medium,
47+
type: FindingType.Suspicious,
48+
})
49+
);
50+
}
51+
}
52+
53+
return findings;
54+
};
55+
56+
export default {
57+
handleTransaction,
58+
};

0 commit comments

Comments
 (0)