MEMO Middleware provides a secure, flexible, and composable data network for developers, enterprises, and more. Users have the flexibility to choose the underlying storage system that suits them.
The middleware will provide storage unit price query and storage plan purchase services, users choose to recharge and purchase packages, obtain storage space, and then upload and download files, and support users to query the list of uploaded files.
Start the middleware service, and the default listening port is 8080. In the example used in this document, the http listening port is set to 8081 and the baseURL is http://localhost:8081. All the following request URLs should be changed according to the actual situation.
Before logging in, users need to get the challenge message
according to the address, and users must set the domain
through the Origin
field.
Request URL: http://localhost:8081/challenge?address={address}
Request Method: GET
Return Parameters: 'challenge message' (defined by EIP-4361)
Request Example:
Note: When calling the challenge interface, you need to specify the domain name in the Origin field of the headers, and currently only the domain name http://memo.io
is supported, otherwise an error will be returned.
Error Code:
HTTP Status Code | Error Code | Error Description |
---|---|---|
500 | InternalError | We encountered an internal error, please try again. |
Users can use the eth account to log in. Users must call the challenge interface first before logging in to get message. Then, the user sign the message used private key. See [EIP-191](https://eips.ethereum.org/EIPS/eip -191) for more details on signature methods. Note: After calling the challenge interface to get the challenge message, the login must be completed within 30 seconds, otherwise the login will fail.
Request URL:http://localhost:8081/login
Request Method:POST
Request Parameters(JSON format):
{ "message": "memo.io wants you to sign in with your Ethereum account:\n0xFD976F1F3dC6413Da5Fed05471eaBB01F4FaaC42\n\n\nURI: http://memo.io\nVersion: 1\nChain ID: 985\nNonce: 12c2ad59e12abbe224cf86741c4bf00a21432fb2673b29a694b72062385f9b5d\nIssued At: 2023-04-23T07:36:10Z", "signature": "0xd5c406dd9ca168cc0894788cd262c3e9bd2f5413f87654c8cb685a9d872b9ab151fe80930c8de4da6adeadf38714387bee65c949ba47efa3eb5ee1335b6cc79400" }
Parameter | Value | Type | Required | Description |
---|---|---|---|---|
message | request signature message | string | yes | the value returned by 'challenge' |
signature | signature information | string | yes | the message after signing the text |
The way to get the signature information is shown in the following code:
package main import ( "crypto/ecdsa" "flag" "fmt" "io/ioutil" "log" "net/http" "time" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" ) func main() { secretKey := flag.String("sk", "", "the sk to signature") flag.Parse() privateKey, err := crypto.HexToECDSA(*secretKey) if err != nil { fmt.Println(err.Error()) return } publicKey := privateKey.Public() publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) if !ok { log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey") } address := crypto.PubkeyToAddress(*publicKeyECDSA).Hex() // get MEMO-Middleware challenge message text, err := Challenge(address) if err != nil { log.Fatal(err) } fmt.Println("message:\n", text) // eip191-signature hash := crypto.Keccak256([]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(text), text))) signature, err := crypto.Sign(hash, privateKey) if err != nil { log.Fatal(err) } sig := hexutil.Encode(signature) fmt.Println("login sig:\n", sig) } func Challenge(address string) (string, error) { client := &http.Client{Timeout: time.Minute} // ip:port should be corresponding to that MEMO-Middleware server is listening url := "http://localhost:8081/challenge" req, err := http.NewRequest("GET", url, nil) if err != nil { return "", err } params := req.URL.Query() params.Add("address", address) req.URL.RawQuery = params.Encode() req.Header.Set("Origin", "https://memo.io") res, err := client.Do(req) if err != nil { return "", err } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { return "", err } if res.StatusCode != http.StatusOK { return "", fmt.Errorf("respond code[%d]: %s", res.StatusCode, string(body)) } return string(body), nil }
Return Parameters(JSON):
{ "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoxLCJhdWQiOiJtZW1vLmlvIiwiZXhwIjoxNjc3NDkwMTgyLCJpYXQiOjE2Nzc0ODkyODIsImlzcyI6Im1lbW8uaW8iLCJzdWIiOiIweEU3RTlmMTJmOTlhRDE3ZDQ3ODZiOUIxMjQ3QzA5N2U2M2NlYUY4RGIifQ.F0asDvu3LH3ccK6LAztBGF1TTzGw7Stc9gBEzVicuE4", "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoyLCJhdWQiOiJtZW1vLmlvIiwiZXhwIjoxNjc4MDk0MDgyLCJpYXQiOjE2Nzc0ODkyODIsImlzcyI6Im1lbW8uaW8iLCJzdWIiOiIweEU3RTlmMTJmOTlhRDE3ZDQ3ODZiOUIxMjQ3QzA5N2U2M2NlYUY4RGIifQ.PDxQ2orOlsES6fvkyR-xWc6M1yBY8RiFTcn8m5AGROc" }
Parameter | Value | Type | Required | Description |
---|---|---|---|---|
accessToken | authentication token | string | yes | within 15 minutes, holding the token can be password-free |
refreshToken | refresh token | string | yes | within 7 days, holding the token can regenerate the access token |
Request Example:
Error Code:
HTTP Status Code | Error Code | Error Description |
---|---|---|
500 | InternalError | We encountered an internal error, please try again. |
401 | Authentication | There is an empty parameter;Can't parse message;Got wrong chain id; Got wrong domain; Got wrong nonce; Got wrong address; Got wrong signature |
Log in with a lens account without calling the interface to get nonce. However, it is necessary to call the challenge interface of lens to obtain the text information to be signed (the format defined by EIP-4361), and use EIP-191 The defined signature method to sign the text information needs to be sent a login request within 30 seconds. When running the middleware service, check whether the account is a Lens account is enabled or disabled.
Request URL: http://localhost:8081/lens/login
Request Method:POST
Request Parameter(JSON Format):
{ "message":"\nmemo.io wants you to sign in with your Ethereum account:\n0x51632235cc673a788E02B30B9F16F7B1D300194C\n\nSign in with ethereum to lens\n\nURI: memo.io\nVersion: 1\nChain ID: 137\nNonce: bcb9b92754e2b900\nIssued At: 2023-03-14T07:26:05.501Z\n ", "signature":"0x..." }
Parameter | Value | Type | Required | Description |
---|---|---|---|---|
message | request signature message | string | yes | call the challenge interface of lens to obtain the text information that needs to be signed |
signature | signature information | string | yes | the message after signing the text |
Call the lens interface to obtain text information as follows:
import( "context" "github.com/machinebox/graphql" ) type Challenge struct { Challenge struct { Text string } `graphql:"challenge(request: $request)"` } type ChallengeRequest struct { Address string `json:"address"` } func ChallengeRequest(address string) (string, error) { client := graphql.NewClient("https://api.lens.dev") req := graphql.NewRequest(` query Challenge($request:ChallengeRequest!) { challenge(request:$request) { text } }`) req.Var("request", ChallengeRequest{ Address: address }) req.Header.Set("Origin", "memo.io") var query Challenge if err := client.Run(context.Background(), req, &query); err != nil { return "", err } return query.Challenge.Text, nil }
Signing using the EIP-191 definition can be borrowed from the code example in 1.2.
Returned Parameter(JSON):
{ "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoxLCJhdWQiOiJtZW1vLmlvIiwiZXhwIjoxNjc3NDkwMTgyLCJpYXQiOjE2Nzc0ODkyODIsImlzcyI6Im1lbW8uaW8iLCJzdWIiOiIweEU3RTlmMTJmOTlhRDE3ZDQ3ODZiOUIxMjQ3QzA5N2U2M2NlYUY4RGIifQ.F0asDvu3LH3ccK6LAztBGF1TTzGw7Stc9gBEzVicuE4", "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoyLCJhdWQiOiJtZW1vLmlvIiwiZXhwIjoxNjc4MDk0MDgyLCJpYXQiOjE2Nzc0ODkyODIsImlzcyI6Im1lbW8uaW8iLCJzdWIiOiIweEU3RTlmMTJmOTlhRDE3ZDQ3ODZiOUIxMjQ3QzA5N2U2M2NlYUY4RGIifQ.PDxQ2orOlsES6fvkyR-xWc6M1yBY8RiFTcn8m5AGROc" }
Parameter | Value | Type | Required | Description |
---|---|---|---|---|
accessToken | authentication token | string | yes | within 15 minutes, holding the token can be password-free |
refreshToken | refresh token | string | yes | within 7 days, holding the token can regenerate the access token |
Request Example:
Error Code:
HTTP Status Code | Error Code | Error Description |
---|---|---|
517 | Address | The address {address} is not registered on lens |
500 | InternalError | We encountered an internal error, please try again. |
401 | Authentication | There is an empty parameter; Got wrong domain; Got wrong chain id; Got wrong address/signature; |
AccessToken is valid for 15 minutes; The validity period of refreshToken is 7 days, when accesToken expires, you need to refresh accessToken according to refreshToken for passwordless authentication login.
Request URL: http://localhost:8081/refresh
Request Method: GET
Request Header Information:
Parameter | Value | Type | Required | Description |
---|---|---|---|---|
Authorization | "Bearer refreshToken" | string | yes | the refreshToken returned by the above login request |
Returned Parameter(JSON):
Parameter | Value | Type | Description |
---|---|---|---|
access token | authentication token | string | the authentication token is regenerated after the validity period has elapsed |
Request Example:
Error Code:
HTTP Status Code | Error Code | Error Description |
---|---|---|
401 | Unauthorized | Illegal fresh token |
After the user is logged in, they can upload files. MEFS uses object storage, and files are uploaded to a bucket with the same name as the login account by default. When you upload a file, if you have not created a bucket with the same name, the middleware will automatically create a bucket with the same name.
Uploading files is limited by the user's recharge amount and storage space, and users can first query the storage unit price and storage plan to recharge to obtain storage space and balance.
Request URL:
choose upload to mefs:http://localhost:8081/mefs/
choose upload to ipfs:http://localhost:8081/ipfs/
Request Method: POST
Request Header Information:
Parameter | Value |
---|---|
Content-Type | multipart/form-data |
Authorization | "Bearer accessToken" |
Request Parameter:
Parameter | Value | Type | Required | Description |
---|---|---|---|---|
file | the file to upload | File | yes | note that select 'File' format |
Returned Parameter(JSON format):
Parameter | Value | Type | Required | Description |
---|---|---|---|---|
cid | the CID of the uploaded file | string | yes | a unique identifier for the file |
Returned Example:
Response:Status 200 { "cid": "bafybeie2ph7iokrckc5iy6xu7npa4xqst6ez5ewb7j2igeilxjaw6sd2qi" }
Request Example:
Error Code:
HTTP Status Code | Error Code | Error Description |
---|---|---|
401 | Authentication | Token is Null; Invalid token payload; Invalid token; |
500 | InternalError | We encountered an internal error, please try again. |
518 | Storage | storage not support |
Users can download the corresponding file according to the CID of the file.
Request URL:
http://ip:port/mefs/$cid
http://ip:port/ipfs/$cid
choose download from mefs: http://localhost:8081/mefs/bafkreifzwcj6vkozz6brwutpxl3hqneran4y5vtvirnbrtw3l2m3jtlgq4
choose download from ipfs: http://localhost:8081/ipfs/bafkreifzwcj6vkozz6brwutpxl3hqneran4y5vtvirnbrtw3l2m3jtlgq4
Request Method: GET
Request Header Information:
none
Request Parameter:
none
Returned Parameter(DataFromReader):
Return file.
Parameter | Description | Value |
---|---|---|
code | status code | 200 |
contentLength | file size | |
contentType | file type | |
reader | io.Reader,the file transfer buffer |
Request Example:
Error Code:
HTTP Status Code | Error Code | Error Description |
---|---|---|
500 | InternalError | We encountered an internal error, please try again. |
518 | Storage | storage not support |
517 | Address | address is null |
Delete uploaded file (only support mefs).
Request URL:
http://ip:port/mefs/delete
Request Method: GET
Request Header Information:
Parameter | Value | Type | Required | Description |
---|---|---|---|---|
Authorization | "Bearer accessToken" | string | yes | If it expires, you can get a new valid accessToken by refreshing the accessToken. |
Request Parameter:
Parameter | Value | Type | Required | Description |
---|---|---|---|---|
mid | file mid | string | yes | the unique identifier of the file, the string returned when file is uploaded |
Returned Parameter(JSON):
Parameter | Type | Description |
---|---|---|
Status | string | deletion success or fail |
Request Example:
Users query the list of files they uploaded.
Request URL:
http://ip:port/mefs/listobjects
http://ip:port/ipfs/listobjects
The file list query of IPFS has not yet been implemented.
Request Method: GET
A list of files for the login account is displayed.
Request Header Information:
Parameter | Value | Type | Required | Description |
---|---|---|---|---|
Authorization | "Bearer accessToken" | string | yes | If it expires, you can get a new valid accessToken by refreshing the accessToken. |
Request Parameter:
none
Returned Parameter(JSON):
Parameter | Type | Description |
---|---|---|
Address | string | account Ethereum wallet address |
Storage | string | mefs or ipfs |
Object | struct | file list |
Each object contains information:
Parameter | Type | Description |
---|---|---|
Name | string | file name |
Size | int64 | file size |
Cid | string | file cid |
ModTime | time | file modification time |
UserDefined | struct | some other information about the file |
UserDefined struct information:
Parameter | Type | Description |
---|---|---|
encryption | string | the file encryption method |
etag | string | file ID mode (default cid) |
Request Example:
Error Code:
HTTP Status Code | Error Code | Error Description |
---|---|---|
401 | Authentication | Token is Null; Invalid token payload; Invalid token; |
516 | Storage | list object error %s |
518 | Storage | storage not support |
Users query their storage balances.
Request URL:http://localhost:8081/account/balance
Request Method: GET
Request Header Information:
Parameter | Value | Type | Required | Description |
---|---|---|---|---|
Authorization | "Bearer accessToken" | string | yes | If it expires, you can get a new valid accessToken by refreshing the accessToken. |
Request Parameter:
none
Returned Parameter(JSON):
Parameter | Value | Type | Required | Description |
---|---|---|---|---|
Address | string | yes | the Ethereum wallet address where you log in to your account | |
Balance | string | yes | the smallest unit number of balances is indicated |
Request Example:
Error Code:
HTTP Status Code | Error Code | Error Description |
---|---|---|
401 | Authentication | Token is Null; Invalid token payload; Invalid token; |
516 | Storage | make bucket error %s; |
518 | Storage | storage not support |
520 | Eth | rpc error |
Users query their storage space, including used, available, free space, and the number of files uploaded.
Request URL:http://localhost:8081/account/getstorage?stype={stype}
Request Method: GET
Request Header Information:
Parameter | Value | Type | Required | Description |
---|---|---|---|---|
Authorization | "Bearer accessToken" | string | yes | If it expires, you can get a new valid accessToken by refreshing the accessToken. |
Request Parameter:
Parameter | Value | Description |
---|---|---|
stype | storage type | supported mefs,ipfs,qiniu now |
Returned Parameter(JSON):
Parameter | Type | Description |
---|---|---|
Used | string | space used |
Available | string | space avail |
Free | string | space free |
Files | string | number of files |
Request Example:
Error Code:
HTTP Status Code | Error Code | Error Description |
---|---|---|
401 | Authentication | Token is Null; Invalid token payload; Invalid token; |
Query the storage unit price of various storage methods.
The server side has not yet been implemented.
Request URL:
Request Method: GET
Request Headers:
Parameter Name | Variable | Type [Length Limit] | Required | Description |
---|---|---|---|---|
Authorization | "Bearer Login-Generated AccessToken" | string | Yes | If expired, new valid AccessToken can be obtained by refreshing AccessToken |
Response Parameters (JSON):
Parameter Name | Variable | Type [Length Limit] | Description |
---|---|---|---|
Time | Storage Time (in seconds) | string | the storage duration included in the package |
Kind | Storage Type | int | the storage kind, includes MEFS and IPFS now |
Buysize | Purchased Storage Space | string | the storage size included in the package |
Amount | Price | string | the price of the package |
State | Status | int | the state of the package, 1 means valid, 0 means invalid |
Request Example:
Error Codes:
HTTP Status Code | Error Code | Error Description |
---|---|---|
401 | Authentication | Token is Null; Invalid token payload; Invalid token; |
Request URL: http://localhost:8081/account/buypkg
Request Method: GET
Request Headers:
Parameter Name | Variable | Type [Length Limit] | Required | Description |
---|---|---|---|---|
Authorization | "Bearer Login-Generated AccessToken" | string | Yes | If expired, new valid AccessToken can be obtained by refreshing AccessToken |
Request Parameters:
Parameter Name | Variable | Type [Length Limit] | Required | Description |
---|---|---|---|---|
amount | Amount | string | Yes | payment amount |
pkgid | Package ID | string | Yes | package id, can get from 'Query Storage Packages' |
chainid | Chain ID | string | Yes | the blockchain ID that executed the purchase package |
Response Parameters (JSON):
Parameter Name | Variable | Type [Length Limit] | Required | Description |
---|---|---|---|---|
Status | Status | string | Yes | buying is success or fails |
Request Example:
Error Codes:
HTTP Status Code | Error Code | Error Description |
---|---|---|
401 | Authentication | Token is Null; Invalid token payload; Invalid token; |
Request URL: http://localhost:8081/account/getbuypkgs
Request Method: GET
Request Headers:
Parameter Name | Variable | Type [Length Limit] | Required | Description |
---|---|---|---|---|
Authorization | "Bearer Login-Generated AccessToken" | string | Yes | If expired, new valid AccessToken can be obtained by refreshing AccessToken |
Request Parameters:
None
Response Parameters (JSON):
Parameter Name | Variable | Type [Length Limit] | Description |
---|---|---|---|
Starttime | Start Time | string | the start service time of the purchased package |
Endtime | End Time | string | the end service time of the purchased package |
Kind | Type | int | 0: MEFS, 1: IPFS |
Buysize | Purchased Size | int | the size of the purchased package |
Amount | Purchase Amount | int | the payment value of the purchased package |
State | Status | int | the package state |
Request Example:
Error Codes:
HTTP Status Code | Error Code | Error Description |
---|---|---|
401 | Authentication | Token is Null; Invalid token payload; Invalid token; |