最后,Even if a contract is removed by selfdestruct, it is still part of the history of the blockchain and probably retained by most Ethereum nodes. So using selfdestruct is not the same as deleting data from a hard disk. 合约删除不会是技术障碍。
type Log struct { // Consensus fields: // address of the contract that generated the event Address common.Address `json:"address" gencodec:"required"` // list of topics provided by the contract. Topics []common.Hash `json:"topics" gencodec:"required"` // supplied by the contract, usually ABI-encoded Data []byte `json:"data" gencodec:"required"`
// Derived fields. These fields are filled in by the node // but not secured by consensus. // block in which the transaction was included BlockNumber uint64 `json:"blockNumber"` // hash of the transaction TxHash common.Hash `json:"transactionHash" gencodec:"required"` // index of the transaction in the block TxIndex uint `json:"transactionIndex" gencodec:"required"` // hash of the block in which the transaction was included BlockHash common.Hash `json:"blockHash"` // index of the log in the receipt Index uint `json:"logIndex" gencodec:"required"`
// The Removed field is true if this log was reverted due to a chain reorganisation. // You must pay attention to this field if you receive logs through a filter query. Removed bool `json:"removed"` }
// NewEVMContext creates a new context for use in the EVM. funcNewEVMContext(from common.Address, blockNum, timeStamp, difficulty int64)vm.Context { // If we don't have an explicit author (i.e. not mining), extract from the header return vm.Context{ CanTransfer: CanTransfer, Transfer: Transfer, GetHash: GetHashFn(), Origin: from, Coinbase: common.Address{}, BlockNumber: new(big.Int).Set(big.NewInt(blockNum)), Time: new(big.Int).Set(big.NewInt(timeStamp)), Difficulty: new(big.Int).Set(big.NewInt(difficulty)), GasLimit: 0xfffffffffffffff, //header.GasLimit, GasPrice: new(big.Int).Set(big.NewInt(10)), } }
// GetHashFn returns a GetHashFunc which retrieves header hashes by number 获取块号码对于的块hash funcGetHashFn()func(n uint64)common.Hash {
returnfunc(n uint64)common.Hash { // If there's no hash cache yet, make one // if cache == nil { // cache = map[uint64]common.Hash{ // ref.Number.Uint64() - 1: ref.ParentHash, // } // } // // Try to fulfill the request from the cache // if hash, ok := cache[n]; ok { // return hash // } // // Not cached, iterate the blocks and cache the hashes // for header := chain.GetHeader(ref.ParentHash, ref.Number.Uint64()-1); header != nil; header = chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) { // cache[header.Number.Uint64()-1] = header.ParentHash // if n == header.Number.Uint64()-1 { // return header.ParentHash // } // } return common.Hash{} } }
// CanTransfer checks wether there are enough funds in the address' account to make a transfer. // This does not take the necessary gas in to account to make the transfer valid. funcCanTransfer(db vm.StateDB, addr common.Address, amount *big.Int)bool { return db.GetBalance(addr).Cmp(amount) >= 0 }
// Transfer subtracts amount from sender and adds amount to recipient using the given Db funcTransfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) { db.SubBalance(sender, amount) db.AddBalance(recipient, amount) }
var normalAddress, _ = hex.DecodeString("123456abc") var hellWorldcontractAddress, _ = hex.DecodeString("987654321") var baseContractAddress, _ = hex.DecodeString("038f160ad632409bfb18582241d9fd88c1a072ba") var normalAccount = common.BytesToAddress(normalAddress) var helloWorldcontactAccont = common.BytesToAddress(hellWorldcontractAddress) var baseContractAccont = common.BytesToAddress(baseContractAddress)
// 基本账户字节码 var baseCodeStr = "608060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632b225f29146100675780638afc3605146100f75780638da5cb5b1461010e578063f2fde38b14610165575b600080fd5b34801561007357600080fd5b5061007c6101a8565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100bc5780820151818401526020810190506100a1565b50505050905090810190601f1680156100e95780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561010357600080fd5b5061010c6101e5565b005b34801561011a57600080fd5b50610123610227565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561017157600080fd5b506101a6600480360381019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061024c565b005b60606040805190810160405280601081526020017f42617365436f6e747261637456302e3100000000000000000000000000000000815250905090565b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156102a757600080fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141515156102e357600080fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff166000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a3505600a165627a7a723058208c3064096245894122f6bcf5e2ee12e30d4775a3b8dca0b21f10d5a5bc386e8b0029"
// hellworld 账户字节码 var hellCodeStr = "6080604052600436106100615763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416634d9b3d5d81146100665780637e8800a7146100ab578063c3f82bc3146100c2578063fb1669ca14610165575b600080fd5b34801561007257600080fd5b5061007b61017d565b6040805173ffffffffffffffffffffffffffffffffffffffff909316835260208301919091528051918290030190f35b3480156100b757600080fd5b506100c06101fa565b005b3480156100ce57600080fd5b506100f073ffffffffffffffffffffffffffffffffffffffff6004351661028f565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561012a578181015183820152602001610112565b50505050905090810190601f1680156101575780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561017157600080fd5b506100c0600435610389565b60408051338152602081018290526005818301527f66756e636b0000000000000000000000000000000000000000000000000000006060820152905160009182917f08c31d20d5c3a5f2cfe0adf83909e6411f43fe97eb091e15c12f3e5a203e8fde9181900360800190a150506000805460001981019091553391565b600080526001602090815260647fa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb4955604080513381529182018190526008828201527f6f6e6c79746573740000000000000000000000000000000000000000000000006060830152517f08c31d20d5c3a5f2cfe0adf83909e6411f43fe97eb091e15c12f3e5a203e8fde9181900360800190a1565b606060008290508073ffffffffffffffffffffffffffffffffffffffff16632b225f296040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401600060405180830381600087803b1580156102fa57600080fd5b505af115801561030e573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052602081101561033757600080fd5b81019080805164010000000081111561034f57600080fd5b8201602081018481111561036257600080fd5b815164010000000081118282018710171561037c57600080fd5b5090979650505050505050565b6000555600a165627a7a72305820c63a859d93a3512b52ccaec75bb9aa146648c41b21c8a0cd0cd2e2c1aede35ed0029"
var helloCode, _ = hex.DecodeString(hellCodeStr) var baseCode, _ = hex.DecodeString(baseCodeStr)
Medusa is an open source headless commerce platform that allows you to create your own store in a matter of minutes. Part of what makes Medusa a good choice for your ecommerce store is its extensibility. Now, it is also possible to create multi-vendor marketplaces using Medusa.
To make things easier for our open source community, Adrien de Peretti, one of our amazing contributors, created a Medusa module that allows you to extend anything and everything you want.
“I’ve been looking for an e-commerce solution that could provide me with some core features while being fully customisable… After some research, where I found that none of the present solutions could provide what I needed, I chose Medusa as it provided me with many of the needed features while being easy to extend. I ended up loving the community atmosphere, especially the proximity with the team, and have been helping those in the community looking for a similar fully-customisable solution by sharing a part of my private project. This is how the medusa-extender was born.” — Adrien de Peretti
In this tutorial, you’ll learn how to install and set up the Medusa Extender module on your Medusa server. You’ll then learn how to use its customization abilities to create a marketplace in your store! The marketplace will have multiple stores or vendors, and each of these stores will be able to add its own products. This tutorial will be the first part of a series that will explore all aspects of creating a marketplace.
What is Medusa Extender
Medusa Extender is an NPM package that you can add to your Medusa store to extend or customize its functionalities. The scope of its customization entails Entities, Repositories, Services, and more.
The Medusa Extender has many use cases aside the marketplace functionality. It can be used in many other use cases, such as adding custom fields, listening to events to perform certain actions like sending emails, customizing Medusa’s validation of request parameters, and more.
What You’ll Be Creating
In this article and the following parts of this series, you’ll learn how to create a marketplace using Medusa and Medusa Extender. A marketplace is an online store that allows multiple vendors to add their products and sell them.
A marketplace has a lot of features, including managing a vendor’s own orders and settings. This part of the tutorial will only showcase how to create stores for each user and attach the products they create to that store.
Code for This Tutorial
If you want to follow along you can find the code for this tutorial in this repository.
Alternatively, if you want to install the marketplace into your existing Medusa store, you can install the Medusa Marketplace plugin. This plugin is created with the code from this tutorial and will be updated with every new part of this series released.
Prerequisites
Before you follow along with this tutorial, make sure you have:
A Medusa server instance was installed. You can follow along with our easy quickstart guide to learn how you can do that.
PostgreSQL installed and your Medusa server connected to it.
Redis installed and your Medusa server connected to it.
sudo systemctl restart redis-server 清楚redis 缓存数据 ➜ ~ redis-cli 127.0.0.1:6379> select (error) ERR wrong number of arguments for 'select' command 127.0.0.1:6379> select 1 OK 127.0.0.1:6379[1]> FLUSHDB OK
➜ ~ sudo /etc/init.d/postgresql star [sudo] password for zhuang: ➜ ~ ➜ ~ sudo -i -u postgres postgres@elementoryos61:~$ psql psql (12.9 (Ubuntu 12.9-0ubuntu0.20.04.1)) Type "help" for help. postgres=# CREATE DATABASE openharbor_marketplace_medusa; CREATE DATABASE postgres=#
Building the Marketplace
Project Setup
In the directory that holds your Medusa server, start by installing Medusa Extender using NPM:
1
npm i medusa-extender
It’s recommended that you use TypeScript in your project to get the full benefits of Medusa-Extender. To do that, create the file tsconfig.json in the root of the Medusa project with the following content:
expressInstance.listen(9000, () => { console.info('Server successfully started on port 9000'); }); }
bootstrap();
This file will make sure to load all the customizations you’ll add next when you run your Medusa server.
Now, Medusa Extender is fully integrated into your Medusa instance and you can start building the Marketplace.
Customize the Store Entity
You’ll start by customizing the Store entity. You’ll need to use it later on to add relations between the store entity and the users and products entities.
By convention, customizations using Medusa Extender are organized in a module-like structure. However, this is completely optional.
In the src directory, create the directory modules in which you’ll store all the customizations in.
Then, create the directory store inside the modules directory. The store directory will hold all customizations related to the Store.
Create a Store Entity
Create the file src/modules/store/entities/store.entity.ts with the following content:
1 2 3 4 5 6 7 8 9
import { Store as MedusaStore } from'@medusajs/medusa/dist'; import { Entity, JoinColumn, OneToMany } from'typeorm'; import { Entity as MedusaEntity } from'medusa-extender';
This uses the decorator @Entity from medusa-extender to customize Medusa’s Store entity. You create a Store class that extends Medusa’s Store entity (imported as MedusaStore ).
You’ll, later on, edit this entity to add the relations between the store and users and products.
Create a Store Repository
Next, you need to override Medusa’s StoreRepository. This repository will return Medusa’s Store entity. So, you need to override it to make sure it returns your Store entity that you just created.
Create the file src/modules/store/repositories/store.repository.ts with the following content:
1 2 3 4 5 6 7 8 9
import { EntityRepository } from'typeorm'; import { StoreRepository as MedusaStoreRepository } from'@medusajs/medusa/dist/repositories/store'; import { Repository as MedusaRepository, Utils } from'medusa-extender'; import { Store } from'../entities/store.entity';
This class will add an additional column store_id of type string and will add a relation to the Store entity.
To add the new column to the user table in the database, you need to create a Migration file. Create the file src/modules/user/migrations/user.migration.ts with the following content:
@Migration() exportdefaultclassaddStoreIdToUser1644946220401implementsMigrationInterface{ name = 'addStoreIdToUser1644946220401';
public async up(queryRunner: QueryRunner): Promise<void> { const query = `ALTER TABLE public."user" ADD COLUMN IF NOT EXISTS "store_id" text;`; await queryRunner.query(query); }
public async down(queryRunner: QueryRunner): Promise<void> { const query = `ALTER TABLE public."user" DROP COLUMN "store_id";`; await queryRunner.query(query); } }
The migration is created using the @Migration decorator from medusa-extender. Notice that the migration name should end with a JavaScript timestamp based on typeorm‘s conventions.
The up method is run if the migration hasn’t been run before. It will add the column store_id to the table user if it doesn’t exist.
You’ll also need to add the relation between the Store and the User entities in src/modules/store/entities/store.entity.ts . Replace the //TODO with the following:
if (!user) { thrownew MedusaError(MedusaError.Types.NOT_FOUND, `User with id: ${userId} was not found`); }
return user as User; } }
This uses the @Service decorator from medusa-extender to override Medusa’s UserService. The class you create to override it will extend UserService.
This new class overrides the retrieve method to ensure that the user returned is the new User entity class you created earlier.
Create a User Middleware
The loggedInUser is not available natively in Medusa. You’ll need to create a Middleware that, when a request is authenticated, registers the logged-in User within the scope.
Create the file src/modules/user/middlewares/loggedInUser.middleware.ts with the following content:
You can use the @Middleware decorator from medusa-extender to create a Middleware that runs on specific requests. This Middleware is run when the request is received from an authenticated user, and it runs for all paths (notice the use of path: '*' ) and for all types of requests (notice the use of method: "all").
Inside the middleware, you retrieve the current user ID from the request, then retrieve the user model and register it in the scope so that it can be accessed from services.
This approach is simplified for the purpose of this tutorial. However, it makes more sense to include this middleware in a separate auth module. Whether you include this middleware in the user module or the auth middleware will not affect its functionality.
Create a Store Service to Handle User Insert Events
You need to ensure that when a user is created, a store is associated with it. You can do that by listening to the User-created event and creating a new store for that user. You’ll add this event handler in a StoreService.
Create the file src/modules/store/services/store.service.ts with the following content:
if (!store) { thrownewError('Unable to find the user store'); }
return store; } }
@OnMedusaEntityEvent.Before.Insert is used to add a listener to an insert event on an entity, which in this case is the User entity. Inside the listener, you create the user using the createForUser method. This method just uses the StoreRepository to create a store.
You also add a helper event retrieve to retrieve the store that belongs to the currently logged-in user.
Notice the use of scope: 'SCOPED' in the @Service decorator. This will allow you to access the logged in user you registered earlier in the scope.
You’ll need to import this new class into the StoreModule. In src/modules/store/store.module.ts add the following import at the beginning:
Then, add the StoreService to the imports array passed to @Module :
1
imports: [Store, StoreRepository, StoreService],
Create a User Subscriber
For the event listener to work, you need to first emit this event in a subscriber. The event will be emitted before a User is inserted. Create the file src/modules/user/subscribers/user.subscriber.ts with the following content:
This will create a subscriber using the EventSubscriber decorator from typeorm. Then, before a user is inserted the OnMedusaEntityEvent.Before.InsertEvent event from medusa-extender is emitted, which will trigger creating the store.
To register the subscriber, you need to create a middleware that registers it. Create the file src/modules/user/middlewares/userSubscriber.middleware.ts with the following content:
This will register the subscriber when a POST request is sent to /admin/users, which creates a new user.
Create a User Router
The last customization left is an optional one. By default, Medusa’s create user endpoint requires you to be authenticated as an admin. In a marketplace use case, you might want users to register on their own and create their own stores. If this is not the case for you, you can skip creating the following class.
Medusa Extender allows you to also override routes in Medusa. In this case, you’ll be adding the /admin/create-user route to accept non-authenticated requests.
Create the file src/modules/user/routers/user.router.ts and add the following content:
You use the @Router decorator from medusa-extender to create a router. This router will accept a routes array which will either be added or override existing routes in your Medusa server. In this case, you override the /admin/create-user route and set requiredAuth to false.
To make sure that the AttachUserSubscriberMiddleware also runs for this new route (so that the before insert user event handlers run for this new route), make sure to add a new entry to the routes array:
You are now ready to test out this customization! In your terminal, run your Medusa server:
1 2 3 4 5
// this is upgrade DB, also the same as scripts in package.json medusa seed -f data/seed.json -m
// this is start medusa server npm start
Or using Medusa’s CLI:
1
medusa develop
After your run your server, you need to use a tool like Postman to easily send requests to your server.
If you didn’t add the UserRouter, you first need to log in as an admin to be able to add users. You can do that by sending a POST request to localhost:9000/admin/auth. In the body, you should include the email and password. If you’re using a fresh Medusa install you can use the following credentials:
Following this request, you can send authenticated requests to the Admin.
Send a POST request to [localhost:9000/admin/users](http://localhost:9000/admin/users) to create a new user. In the body, you need to pass the email and password of the new user:
The request will return a user object with the details of the new user:
![Create User Result](Open source ecommerce platform for multi-vendor marketplaces/1.png)
Notice how there’s a store_id field now. If you try to create a couple of users, you’ll see that the store_id will be different each time.
Customize the Products Entity
Similar to how you just customized the User entity, you need to customize the Product entity to also hold the store_id with the relationship as well. You’ll then customize the ProductService as well as other classes to make sure that, when a product is created, the store ID of the user creating it is attached to it. You’ll also make sure that when the list of products is fetched, only the products that belong to the current user’s store are returned.
Create a Product Entity
Create the file src/modules/product/entities/product.entity.ts with the following content:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
import { Product as MedusaProduct } from'@medusajs/medusa/dist'; import { Column, Entity, Index, JoinColumn, ManyToOne } from'typeorm'; import { Entity as MedusaEntity } from'medusa-extender'; import { Store } from'../../store/entities/store.entity';
This will override Medusa’s Product entity to add the store_id field and relation to the Store entity.
You need to also reflect this relation in the Store entity, so, in src/modules/store/entities/store.entity.ts add the following code below the relation with the User entity you previously added:
This will override Medusa’s ProductRepository to return your new Product entity.
Create a Product Service
Now, you’ll add the customization to ensure that only the products that belong to the currently logged-in user are returned when a request is sent.
Since you created the LoggedInUserMiddleware earlier, you can have access to the logged-in user from any service through the container object passed to the constructor of the service.
Create the file src/modules/product/services/product.service.ts with the following content:
This will override the prepareListQuery method in Medusa’s ProductService, which this new class extends, to get the logged-in user. Then, if the user is retrieved successfully the key store_id is added to the selector object to filter the products by the user’s store_id.
Create a Product Module
That’s all the customization you’ll do for now. You just need to import all these files into a Product module.
Create src/modules/product/product.module.ts with the following content:
You can go ahead and test it out now. Run the server if it isn’t running already and log in with the user you created earlier by sending the credentials to localhost:9000/admin/auth.
After that, send a GET request to localhost:9000/admin/products. You’ll receive an empty array of products as the current user does not have any products yet.
![Result of Get Products](Open source ecommerce platform for multi-vendor marketplaces/2.png)
You’ll now add the necessary customization to attach a store ID to a newly created product.
To listen to the product created event, create the file src/modules/product/subscribers/product.subscriber.ts with the following content:
Then, you need to register this Subscriber using Middleware. Create the file src/modules/product/middlewares/product.middleware.ts with the following content:
This will listen to the Insert event using the @OnMedusaEntityEvent decorator from medusa-extender. It will then use the logged-in user and attach the user’s store_id to the newly created product.
Add Middleware to Product Module
Finally, make sure to import the new middleware at the beginning of src/modules/product/product.module.ts:
You’re ready to add products into a store now! Run the server if it’s not running and make sure you’re logged in with the user you created earlier. Then, send a POST request to [localhost:9000/admin/products](http://localhost:9000/admin/products) with the following body:
1 2 3 4
{ "title": "my product", "options": [] }
This is the minimum structure of a product. You can rename the title to anything you want.
After you send the request, you should receive a Product object where you can see the store_id is set to the same store_id of the user you’re logged in with.
![Add Product Request Result](Open source ecommerce platform for multi-vendor marketplaces/3.png)
Now, try sending a GET request to [localhost:9000/admin/products](http://localhost:9000/admin/products) as you did earlier. Instead of an empty array, you’ll see the product you just added.
![Retrieve Products](Open source ecommerce platform for multi-vendor marketplaces/4.png)
Testing it Out Using Medusa’s Admin
If you also have a Medusa Admin instance installed, you can also test this out. Log in with the user you created earlier and you’ll see that you can only see the product they added.
![Admin Dashboard](Open source ecommerce platform for multi-vendor marketplaces/5.png)
Conclusion
In this tutorial, you learned the first steps of creating a Marketplace using Medusa and Medusa Extender! In later points, you’ll learn about how you can add settings, manage orders, and more!
Be sure to support Medusa Extender and check the repository out for more details!
Should you have any issues or questions related to Medusa, then feel free to reach out to the Medusa team via Discord. You can also contact Adrien @adrien2p for more details or help regarding Medusa Extender.
How can I finish (or complete) an order? I already run Capture an Order -> Create a Fulfillment -> Create a Shipment successfully, but the order status is “pending” in DB. (in Ubuntu / medusa version v1.7.12)
Answer
You can create a simple subscriber, that listens for order completion events: