Quickstart¶
The BNB Greenfield JavaScript SDK is designed for front-end environments and provides an API for interacting with BNB Greenfield decentralized storage. It offers a range of operations, including retrieving permission details, gas fees, etc. The SDK also includes a crypto component for signing transactions and sending them to BNB Greenfield.
However, it should be noted that this SDK does not include methods for interacting with BNB Smart Chain (BSC). For a comprehensive understanding of available operations, refer to the API Reference.
Install¶
npm install @bnb-chain/greenfield-js-sdk
Usage¶
To utilize the SDK functionality, users need to instantiate a client object from the SDK. This client object serves as the interface to interact with BNB Greenfield and perform the desired operations.
Create client¶
import { Client } from '@bnb-chain/greenfield-js-sdk'
export const client = Client.create('https://gnfd-testnet-fullnode-tendermint-ap.bnbchain.org', '5600');
The SDK offers two types of operations - sending transactions to BNB Greenfield, allowing users to modify the state of the blockchain; the second type enables users to send queries and retrieve metadata information about objects stored on the blockchain.
The SDK consists of two parts:
- Chain: Greenfield Chain API
- Storage Provider: Greenfield Storage Provider API
Transactions¶
1. Transaction construction¶
The SDK offers functionality for transferring tokens between accounts, providing a straightforward and convenient way to perform token transfers. With the SDK, users can easily initiate and execute token transfers within the desired accounts, streamlining the process of managing and exchanging tokens.
The SDK includes functionality for simulating and broadcasting transactions, allowing users to retrieve essential information related to gas fees, and sending the transaction over network.
const { simulate, broadcast } = await client.account.transfer({
fromAddress: address,
toAddress: transferInfo.to,
amount: [
{
denom: 'BNB',
amount: ethers.utils.parseEther(transferInfo.amount).toString(),
},
],
});
2. Simulate Transactions¶
This function returns the estimated gas limit, gas price, and overall gas fee.
// simulate tx
const simulateInfo = await simulate({
denom: 'BNB',
});
Example output
{
"gasLimit":2400,
"gasPrice":"5000000000",
"gasFee":"0.000012"
}
3. Broadcast Transactions¶
Use the API endpoint to send the transaction data to the blockchain network.
// broadcast tx
// This includes details such as gas limit, gas price, and overall gas fee.
const broadcastRes = await broadcast({
denom: 'BNB',
gasLimit: Number(simulateInfo.gasLimit),
gasPrice: simulateInfo.gasPrice,
payer: address,
granter: '',
});
NOTICE: Signature mode for Broadcast
¶
broadcast
use window.ethereum
as signature provider by default.
If you want to use others, you can set signTypedDataCallback
:
// TrustWallet
const broadcastRes = await broadcast({
//...
signTypedDataCallback: async (addr: string, message: string) => {
return await window.trustwallet.request({
method: 'eth_signTypedData_v4',
params: [addr, message],
});
}
});
If you broadcast in Nodejs, you can broadcast a tx by privateKey
:
const broadcastRes = await broadcast({
//...
privateKey: '0x.......'
});
Example output after broadcast your transaction:
transaction result
{
"code":0,
"height":449276,
"txIndex":0,
"events":[
{
"type":"coin_spent",
"attributes":[
"Array"
]
},
{
"type":"coin_received",
"attributes":[
"Array"
]
},
{
"type":"transfer",
"attributes":[
"Array"
]
},
{
"type":"message",
"attributes":[
"Array"
]
},
{
"type":"tx",
"attributes":[
"Array"
]
},
{
"type":"tx",
"attributes":[
"Array"
]
},
{
"type":"tx",
"attributes":[
"Array"
]
},
{
"type":"message",
"attributes":[
"Array"
]
},
{
"type":"coin_spent",
"attributes":[
"Array"
]
},
{
"type":"coin_received",
"attributes":[
"Array"
]
},
{
"type":"transfer",
"attributes":[
"Array"
]
},
{
"type":"message",
"attributes":[
"Array"
]
},
{
"type":"greenfield.payment.EventStreamRecordUpdate",
"attributes":[
"Array"
]
},
{
"type":"greenfield.payment.EventStreamRecordUpdate",
"attributes":[
"Array"
]
},
{
"type":"greenfield.payment.EventStreamRecordUpdate",
"attributes":[
"Array"
]
},
{
"type":"greenfield.storage.EventCreateBucket",
"attributes":[
"Array"
]
}
],
"rawLog":"..",
"transactionHash":"D304242145ED9B44F05431C3798B3273CF2A907E6AE1CA892759985C900D6E72",
"gasUsed":2400,
"gasWanted":2400
}
4. Multi-Transactions¶
The SDK also provides support for bundling multiple operations into a single transaction, thereby reducing gas fees. This feature allows users to optimize their transactions by combining several operations together, minimizing the overall gas cost associated with executing them individually. By leveraging this functionality, users can effectively manage their gas fees and enhance the efficiency of their transactions within the blockchain network using the SDK.
const createGroupTx = await client.group.createGroup(params);
const mirrorGroupTx = await client.crosschain.mirrorGroup({
groupName,
id,
operator,
});
const principal = {
type: PermissionTypes.PrincipalType.PRINCIPAL_TYPE_GNFD_GROUP,
value: GRNToString(newGroupGRN(address as string, groupName)),
};
const statement: PermissionTypes.Statement = {
effect: PermissionTypes.Effect.EFFECT_ALLOW,
actions: [PermissionTypes.ActionType.ACTION_GET_OBJECT],
resources: [
GRNToString(
type === 'Data'
? newObjectGRN(bucketName, name)
: newObjectGRN(bucketName, '*'),
),
],
};
const policyTx = await client.object.putObjectPolicy(bucketName, name, {
operator: address,
statements: [statement],
principal,
});
const { simulate, broadcast } = await multiTx([
createGroupTx,
mirrorGroupTx,
policyTx,
]);
Querying Metadata¶
- Account info
const { client, selectSp, generateString } = require('./client');
const { ACCOUNT_ADDRESS, ACCOUNT_PRIVATEKEY } = require('./env');
const Long = require('long');
(async () => {
// get account info
const addrInfo = await client.account.getAccount(ACCOUNT_ADDRESS);
console.log('address is', addrInfo);
})
Example output
{
"address":"0x525482AB3922230e4D73079890dC905dCc3D37cd",
"pubKey":{
"typeUrl":"/cosmos.crypto.eth.ethsecp256k1.PubKey",
"value":"CiECKuOEfCNFxnfiinnIIoe0OSf3VEOAU5jxwmZscfpOaW4="
},
"accountNumber":"5012",
"sequence":"9"
}
Storage Provider Client¶
https://github.com/bnb-chain/greenfield-storage-provider/tree/master/docs/storage-provider-rest-api
In addition, the SDK provides support for querying the list of storage providers available and offers generic search capabilities for exploring metadata attributes.
SDK support two authentication type:
- ECDSA: It is usually used on Node.js (Because it need to use a private key)
- EDDSA: It is usually used in a browser
getBucketReadQuota
as example:
// generate seed:
const allSps = await getAllSps();
const offchainAuthRes = await client.offchainauth.genOffChainAuthKeyPairAndUpload(
{
sps: allSps,
chainId: GREEN_CHAIN_ID,
expirationMs: 5 * 24 * 60 * 60 * 1000,
domain: window.location.origin,
address: 'your address',
},
provider: 'wallet provider',
);
// request sp api
const bucketQuota = await client.bucket.getBucketReadQuota(
{
bucketName,
},
{
type: 'EDDSA',
seed: offchainAuthRes.seedString,
domain: window.location.origin,
address: 'your address',
},
);
// Node.js:
// request sp api
const bucketQuota = await client.bucket.getBucketReadQuota(
{
bucketName,
},
{
type: 'ECDSA',
privateKey: '0x....'
},
);
Others functions:
List Storage Providers¶
export const getSps = async () => {
const sps = await client.sp.getStorageProviders();
const finalSps = (sps ?? []).filter(
(v: any) => v?.description?.moniker !== 'QATest',
);
return finalSps;
};
Search for objects¶
It’s important to note that even if an object is set to private, its metadata remains publicly accessible. This metadata includes information such as file size, file type, and file name.
export const searchKey = async (key: string) => {
try {
return await client.sp.listGroup(key, `${DAPP_NAME}_`, {
sourceType: 'SOURCE_TYPE_ORIGIN',
limit: 1000,
offset: 0,
});
} catch (e) {
return [];
}
Examples¶
Now let’s make a complete example, includes:
- create bucket
- create object and upload it to the bucket
- download the object
Prepare¶
To begin, create an account and deposit tokens into it on Greenfield. Follow the instructions provided in Token Transfer. Please be aware that if your account does not have any BNB, the transaction will not be executed.
Choose Storage Provider¶
Storing data is one of the most important features of Greenfield. All storage-related apis require the storage provider to be chose.
const spList = await client.sp.getStorageProviders();
const sp = {
operatorAddress: spList[0].operatorAddress,
endpoint: spList[0].endpoint,
};
ECDSA / OffChainAuth¶
ECDSA require users to use private key for authentication.
OffChainAuth is used to authenticate yourself to the provider.
Code can’t access user’s private key on browser, so we use
OffChainAuth
on browser and useECDSA
on Nodejs.
// MetaMask
const provider = window.ethereum;
const offchainAuthRes = await client.offchainauth.genOffChainAuthKeyPairAndUpload({
sps: {
address: sp.operatorAddress,
endpoint: sp.endpoint,
},
chainId: '5600',
expirationMs: 5 * 24 * 60 * 60 * 1000,
domain: window.location.origin,
// your wallet account
address: '0x..',
}, provider);
Info
Nodejs don’t need offchainauth.
// your account
const ACCOUNT_ADDRESS = '0x....'
// your account's private key
const ACCOUNT_PRIVATEKEY = '0x....'
1. Create Bucket¶
1.1 construct create bucket tx¶
Bucket can be private or public, you can customize it with options (visibility
):
VISIBILITY_TYPE_PUBLIC_READ
VISIBILITY_TYPE_PRIVATE
const createBucketTx = await client.bucket.createBucket(
{
bucketName: 'bucket_name',
creator: address,
visibility: VisibilityType.VISIBILITY_TYPE_PUBLIC_READ,
chargedReadQuota: Long.fromString('0'),
primarySpAddress: sp.operatorAddress,
paymentAddress: address,
}
);
1.2 simulate create bucket tx¶
const createBucketTxSimulateInfo = await createBucketTx.simulate({
denom: 'BNB',
});
1.3 broadcast create bucket tx¶
const res = await createBucketTx.broadcast({
denom: 'BNB',
gasLimit: Number(simulateInfo?.gasLimit),
gasPrice: simulateInfo?.gasPrice || '5000000000',
payer: address,
granter: '',
});
const res = await createBucketTx.broadcast({
denom: 'BNB',
gasLimit: Number(createBucketTxSimulateInfo?.gasLimit),
gasPrice: createBucketTxSimulateInfo?.gasPrice || '5000000000',
payer: ACCOUNT_ADDRESS,
granter: '',
// highlight-start
privateKey: ACCOUNT_PRIVATEKEY,
// highlight-end
});
2. Create Object¶
2.1 construct create object tx¶
Like the visibility of bucket, object also has a visibility:
VISIBILITY_TYPE_PUBLIC_READ
VISIBILITY_TYPE_PRIVATE
Getting file’s checksum need reed-solomon:
import { ReedSolomon } from '@bnb-chain/reed-solomon';
const rs = new ReedSolomon();
// file is File type
const fileBytes = await file.arrayBuffer();
const expectCheckSums = rs.encode(new Uint8Array(fileBytes));
const fs = require('node:fs');
const { NodeAdapterReedSolomon } = require('@bnb-chain/reed-solomon/node.adapter');
const filePath = './CHANGELOG.md';
const fileBuffer = fs.readFileSync(filePath);
const rs = new NodeAdapterReedSolomon();
const expectCheckSums = await rs.encodeInWorker(__filename, Uint8Array.from(fileBuffer));
const createObjectTx = await client.object.createObject(
{
bucketName: 'bucket_name',
objectName: 'object_name',
// user's account address
creator: '0x...',
visibility: VisibilityType.VISIBILITY_TYPE_PRIVATE,
contentType: 'json',
redundancyType: RedundancyType.REDUNDANCY_EC_TYPE,
payloadSize: Long.fromInt(13311),
expectChecksums: expectCheckSums.map((x) => bytesFromBase64(x)),
}
);
2.2 simulate create object tx¶
const createObjectTxSimulateInfo = await createObjectTx.simulate({
denom: 'BNB',
});
2.3 broadcast create object tx¶
const res = await createObjectTx.broadcast({
denom: 'BNB',
gasLimit: Number(simulateInfo?.gasLimit),
gasPrice: simulateInfo?.gasPrice || '5000000000',
payer: address,
granter: '',
});
const createObjectTxRes = await createObjectTx.broadcast({
denom: 'BNB',
gasLimit: Number(createObjectTxSimulateInfo?.gasLimit),
gasPrice: createObjectTxSimulateInfo?.gasPrice || '5000000000',
payer: ACCOUNT_ADDRESS,
granter: '',
// highlight-start
privateKey: ACCOUNT_PRIVATEKEY,
// highlight-end
});
2.4 upload object¶
const uploadRes = await client.object.uploadObject(
{
bucketName: createObjectInfo.bucketName,
objectName: createObjectInfo.objectName,
body: file,
txnHash: txHash,
},
// highlight-start
{
type: 'EDDSA',
domain: window.location.origin,
seed: offChainData.seedString,
address,
},
// highlight-end
);
const uploadRes = await client.object.uploadObject(
{
bucketName: bucketName,
objectName: objectName,
body: createFile(filePath),
txnHash: createObjectTxRes.transactionHash,
},
// highlight-start
{
type: 'ECDSA',
privateKey: ACCOUNT_PRIVATEKEY,
}
// highlight-end
);
// convert buffer to file
function createFile(path) {
const stats = fs.statSync(path);
const fileSize = stats.size;
return {
name: path,
type: '',
size: fileSize,
content: fs.readFileSync(path),
}
}
3. Download Object¶
const res = await client.object.downloadFile(
{
bucketName: 'bucket_name',
objectName: 'object_name',
},
// highlight-start
{
type: 'EDDSA',
address,
domain: window.location.origin,
seed: offChainData.seedString,
},
// highlight-end
);
const res = await client.object.getObject(
{
bucketName: 'bucket_name',
objectName: 'object_name',
},
// highlight-start
{
type: 'ECDSA',
privateKey: ACCOUNT_PRIVATEKEY,
}
// highlight-end
);
// res.body is Blob
console.log('res', res)
const buffer = Buffer.from([res.body]);
fs.writeFileSync('your_output_file', buffer)