Code for Distributor Model: LT8K-150


Code for Distributor Model: HDC-8K050



Some generous anon converted all the weights. Grab them here: and

Huggingface implementation is available now!

gh pr checkout 21955 inside the transformers directory. You'll need to clone it first: git clone

llamanon here.
This guide is supposed to be understandable to the average /aicg/ user (possibly retarded). This is for Linux obviously - I don't know how to run bitsandbytes on Windows, and I don't have a Windows machine to test it on.

If you're on Windows, I recommend using Oobabooga. It now supports LLaMA with 8bit.

Why don't I recommend using oobabooga? It's terrible at memory management and according to my tests, you'll use less VRAM with meta's own inference code as opposed to ooba's.

여기 라마논.
이 가이드는 평균 /aicg/ 사용자(지체 가능성이 있음)가 이해할 수 있도록 되어 있습니다.이것은 Linux용입니다.Windows에서 비트나 바이트를 실행하는 방법을 모르기 때문에 테스트할 수 있는 Windows 머신이 없습니다.

Windows  사용하고 있는 경우는, Oobobooga 를 사용하는 것을 추천합니다.8비트로 LLaMA를 지원하게 되었습니다.

OOBabooga를 추천하는 것은 어떨까요?메모리 관리 능력이 형편없고, 제 테스트에 따르면, oba가 아닌 메타의 자체 추론 코드로 VRAM을 덜 사용하게 될 것입니다.

Download LLaMA weights

Get the .torrent

Please download and seed all the model weights if you can. If you want to run a single model, don't forget to download the tokenizer.model file too.

Set up Conda and create an environment for LLaMA

I hate conda too, but it's the official method recommended by meta for some reason, and I don't want to deviate.
저도 콘다는 싫지만 메타가 추천하는 공식 방법이라서 벗어나고 싶지 않아요.

Set up Conda

  1. Open a terminal and run: 

2. Run

chmod +x

3. Run 



4. Go with the default options. When it shows you the license, hit q to continue the installation.
기본 옵션을 사용합니다.라이선스가 표시되면q설치를 계속합니다.


5. Refresh your shell by logging out and logging in back again.
로그아웃한 후 다시 로그인하여 셸을 새로 고칩니다
I think closing the terminal works too, but I don't remember. Try both.
터미널을 닫으면 되는 것 같은데 기억이 안 나네요.둘 다 먹어봐.


Create env and install dependencies (환경 및 설치 의존 관계 생성)

  1. Create an env: 환경을 만듭니다.
    conda create -n llama
  2. Activate the env: env를 활성화합니다.
    conda activate llama
  3. Install the dependencies:의존 관계를 인스톨 합니다.
    conda install torchvision torchaudio pytorch-cuda=11.7 git -c pytorch -c nvidia
    pip3 install torch torchvision torchaudio --extra-index-url
  4. Clone the INT8 repo by tloen: INT8 repo를 tloen으로 복제합니다.
    git clone && cd llama-int8
  5. Install the requirements: 요건을 인스톨 합니다.
    pip install -r requirements.txt pip install -e .

Create a swapfile 스왑 파일 생성

Loading the weights for 13B and higher models needs considerable amount of DRAM. IIRC it takes about 50GB for 13B, and over a 100GB for 30B. You'll need a swapfile to take care of excess memory usage. This is only used for the loading process; inference is unaffected (as long as you meet the VRAM requirements). 
13B 이상 모델의 무게를 로드하려면 상당한 양의 DRAM이 필요합니다.IIRC는 13B의 경우 약 50GB, 30B의 경우 100GB 이상이 소요됩니다.과도한 메모리 사용량을 처리하려면 스왑 파일이 필요합니다.이것은 로드 프로세스에만 사용됩니다.추론은 영향을 받지 않습니다(VRAM 요건을 충족하는 한).

  1. Create a swapfile: 스왑 파일 생성:
    sudo dd if=/dev/zero of=/swapfile bs=4M count=13000 status=progressThis will create about ~50GB swapfile. Edit the count to your preference. 13000 means 4MBx13000.
    그러면 약 50GB의 스왑 파일이 생성됩니다.를 편집하다count당신의 취향에 따라.13000은 4MBx13000을 의미합니다.
  2. Mark it as swap: 스왑으로 표시
    sudo mkswap /swapfile
  3. Activate it: 활성화
    sudo swapon /swapfile

If you want to delete it, simply run sudo swapoff /swapfile and then rm /swapfile.
삭제할 경우 실행하기만 하면 됩니다.sudo swapoff /swapfile그리고 나서.rm /swapfile.

Run the models 모델 실행

I'll assume your LLaMA models are in 당신의 LLama 모델은 현재

  1. Open a terminal in your llama-int8 folder (the one you cloned).  터미널에서 폴더(복제된 폴더)룰 엽니다.
  2. Run: 
    python --ckpt_dir ~/Downloads/LLaMA/7B --tokenizer_path ~/Downloads/LLaMA/tokenizer.model --max_batch_size=1
  3. You're done. Wait for the model to finish loading and it'll generate a prompt.
    모든 작업이 완료됩니다.모델의 로드가 완료될 때까지 기다리면 프롬프트가 생성됩니다.

Add custom prompts  커스텀 프롬프트 추가

By default, the llama-int8 repo has a short prompt baked in to
디폴트로는 lama-int8 repo에는 다음과 같은 짧은 프롬프트가 포함되어 있습니다.

  1. Open the "" file in the "llama-int8" directory.
  2. Navigate to line 136. It starts with triple quotations, """.
    136행으로 이동합니다.처음에는 세 개의 인용구로 시작하지만
  3. Replace the current prompt with whatever you have in mind.
    현재 프롬프트를 원하는 프롬프트로 바꿉니다.

I'm getting shitty results!  결과가 안 좋아!

The inference code sucks for LLaMA. It only supports Temperature and Top_K. We'll have to wait until HF implements support for it (already in the works) so that it can properly show its true potential.
추론 코드가 LLama에겐 최악이야Temperature와 Top_K만 지원합니다.HF가 그 진정한 잠재력을 제대로 발휘할 수 있도록 지원(이미 진행 중)을 실시할 때까지 기다려야 합니다.


LLaMA INT8 Inference guide

DOWNLOAD THE CONVERTED WEIGHTS Some generous anon converted all the weights. Grab them here: and Huggingface implementation is available now! You can now convert the weights to a HF form




3. CUDA 메모리 부족 오류

그example.py파일은 다음 설정에 따라 캐시를 사전 준비합니다.

model_args: ModelArgs = ModelArgs(max_seq_len=max_seq_len, max_batch_size=max_batch_size, **params)

모델 무게(7B 모델)에 대해 14GB의 메모리를 차지하므로 2 * 2 * n_layer * max_batch_size * max_seq_len * n_heads * head_dim 바이트를 저장하는 디코딩 캐시에 16GB를 사용할 수 있습니다.

기본 파라미터의 경우 이 캐시는 7B 모델의 경우 약 17GB(2 * 2 * 32 * 32 * 1024 * 32 * 128)였습니다.

명령줄 옵션이 추가되어 있습니다.example.py디폴트를 변경했습니다.max_seq_len30GB GPU에서 디코딩이 가능한 최대 512입니다.

사용의 하드웨어에 따라서, 이러한 설정을 내려 주세요.

TON is an innovative blockchain that incorporates novel concepts in smart contract design. Coming a number of years after Ethereum, the builders of TON had the advantage of being able to evaluate the successes and failures of the Ethereum Virtual Machine (EVM) model. For more on this, check out six unique aspects of TON blockchain that will surprise Solidity developers.

In this article, we’ll go through a couple of the most interesting features of the TON blockchain, then we’ll go through a list of best practices for developers programming smart contracts in FunC.

Contract Sharding

When developing contracts for the EVM, you generally break up the project into several contracts for convenience. In some cases, it is possible to implement all the functionality in one contract, and even where contract splitting was necessary (for example, Liquidity Pairs in the Automated Market Maker) this did not lead to any special difficulties. Transactions are executed in their entirety: either everything works out, or everything reverts.

In TON, it is strongly recommended to avoid “unbounded data structures” and split a single logical contract into small pieces, each of which manages a small amount of data. The basic example is the implementation of TON Jettons. This is TON's version of Ethereum's ERC-20 token standard. Briefly, we have:

  1. One jetton-minter that stores total_supply, minter_address, and a couple of refs: token description (metadata) and jetton_wallet_code.
  2. And a lot of jetton-wallet, one for each owner of these jettons. Each such wallet stores only the owner's address, their balance, jetton-minter address, and a link to jetton_wallet_code.

This is necessary so that the transfer of Jettons occurs directly between wallets and does not affect any high-load addresses, which is fundamental for the parallel processing of transactions.

That is, get ready so that your contract turns into a "group of contracts", and they will actively interact with each other.

What follows from this?

Partial Execution of Transactions is Possible

A new unique property appears in the logic of your contract: partial execution of transactions.

For example, consider the message flow of a standard TON Jetton:

TON Jetton message flow

As follows from the diagram,

  1. sender sends an op::transfer message to its wallet (sender_wallet)
  2. sender_wallet reduces the token balance
  3. sender_wallet sends an op::internal_transfer message to the recipient's wallet (destination_wallet)
  4. destination_wallet increases its token balance
  5. destination_wallet sends op::transfer_notification to its owner (destination)
  6. destination_wallet returns excess gas with op::excesses message on response_destination (usually sender)

Note that if the destination_wallet was unable to process the op::internal_transfer message (an exception occurred or the gas ran out), then this part and subsequent steps will not be executed. But the first step (reducing the balance in sender_wallet) will be completed. The result is a partial execution of the transaction, an inconsistent state of the Jetton, and, in this case, the loss of money.

In the worst case scenario, all the tokens can be stolen in this way. Imagine that you first accrue bonuses to the user, and then send an op::burn message to their Jetton wallet, but you cannot guarantee that the op::burn will be processed successfully.

Tip #1: Always Draw Message Flow Diagrams

Even in a simple contract like a TON Jetton, there are already quite a few messages, senders, receivers, and pieces of data contained in messages. Now imagine how it looks when you’re developing something a little more complex, like a decentralized exchange (DEX) where the number of messages in one workflow can exceed ten.

An example message flow demonstrating the importance of visualization

At CertiK, we use the DOT language to describe and update such diagrams during the course of the audit. Our auditors find that this helps them visualize and understand the complex interactions within and between contracts.

Tip #2: Avoid Fails and Catch Bounced Messages

Using the message flow, first define the entry point. This is the message that starts the cascade of messages in your group of contracts (“consequences”). It is here that everything needs to be checked (payload, gas supply, etc.) in order to minimize the possibility of failure in subsequent stages.

If you are not sure whether it will be possible to fulfill all your plans (for example, whether the user has enough tokens to complete the deal), it means that the message flow is probably built incorrectly.

In subsequent messages (consequences), all throw_if()/throw_unless() will play the role of asserts rather than actually checking something.

Many contracts also process bounced messages just in case.

For example, in TON Jetton, if the recipient's wallet cannot accept any tokens (it depends on the logic for receiving), then the sender's wallet will process the bounced message and return the tokens to its own balance.

() on_bounce (slice in_msg_body) impure {
    in_msg_body~skip_bits(32);  ;;0xFFFFFFFF

    (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data();

    int op = in_msg_body~load_op();

    throw_unless(error::unknown_op, (op == op::internal_transfer) | (op == op::burn_notification));

    int query_id = in_msg_body~load_query_id();
    int jetton_amount = in_msg_body~load_coins();

    balance += jetton_amount;
    save_data(balance, owner_address, jetton_master_address, jetton_wallet_code);

In general, we recommend processing bounced messages, however, they can’t be used as a means of full protection from failed message processing and incomplete execution.

It takes gas to send a bounced message and process it, and if there isn’t enough provided by the sender, then no bounced.

Secondly, TON does not provide for a chain of jumps. This means a bounced message can't be re-bounced. For example, if the second message is sent after the entry message, and the second one triggers the third one, then the entry contract will not be aware of the failure of processing the third message. Similarly, if the processing of the first sends the second and the third, then the failure of the second will not affect the processing of the third.

Tip #3: Expect a Man-in-the-Middle of the Message Flow

A message cascade can be processed over many blocks. Assume that while one message flow is running, an attacker can initiate a second one in parallel. That is, if a property was checked at the beginning (e.g. whether the user has enough tokens), do not assume that at the third stage in the same contract they will still satisfy this property.

Recommendation #4: Use a Carry-Value Pattern

It follows from the previous paragraph that messages between contracts must carry valuables.

In the same TON Jetton, this is demonstrated: sender_wallet subtracts the balance and sends it with an op::internal_transfer message to destination_wallet, and it, in turn, receives the balance with the message and adds it to its own balance (or bounces it back).

And here is an example of incorrect implementation. Why can't you find out your Jetton balance on-chain? Because such a question does not fit the pattern. By the time the response to the op::get_balance message reaches the requester, this balance could already have been spent by someone.

In this case, you can implement an alternative:

Carry-value pattern usage example

  1. master sends a message op::provide_balance to the wallet
  2. wallet zeroes its balance and sends back op::take_balance
  3. master receives the money, decides if it has enough, and either uses it (debits something in return) or sends it back to wallet
Tip #5: Return Value Instead of Rejecting

From the previous observation, it follows that your group of contracts will often receive not just a request, but a request along with a value. So you can't just refuse to execute the request (via throw_unless()), you have to send the Jettons back to the sender.

For example, a typical flow start (see TON Jetton message flow):

  1. sender sends an op::transfer message via sender_wallet to your_contract_wallet, specifying forward_ton_amount and forward_payload for your contract.
  2. sender_wallet sends an op::internal_transfer message to your_contract_wallet.
  3. your_contract_wallet sends an op::transfer_notification message to your_contract, delivering forward_ton_amount, forward_payload, and the sender_address and jetton_amount.
  4. And here in your contract in handle_transfer_notification() the flow begins.

There you need to figure out what kind of request it was, whether there is enough gas to complete it, and whether everything is correct in the payload. At this stage, you should not use throw_if()/throw_unless(), because then the Jettons will simply be lost, and the request will not be executed. It's worth using the try-catch statements available starting from FunC v0.4.0.

If something does not meet the expectations of your contract, the Jettons must be returned.

We found an example of this vulnerable implementation in a recent audit.

() handle_transfer_notification(...) impure {
    int jetton_amount = in_msg_body~load_coins();
    slice from_address = in_msg_body~load_msg_addr();
    slice from_jetton_address = in_msg_body~load_msg_addr();

    if (msg_value < gas_consumption) { ;; not enough gas provided
        if (equal_slices(from_jetton_address, jettonA_address)) {
            var msg = begin_cell()
                .store_uint(0x18, 6)
                .store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1)

As you can see, here the "return" is sent to jettonA_wallet_address, not sender_address. Since all decisions are made based on the analysis of in_msg_body, an attacker can forge a fake message and extract the money. Always send the return to sender_address.

If your contract accepts Jettons, it is impossible to know if it was indeed the expected Jetton that came, or just an op::transfer_notification from someone.

If your contract receives unexpected or unknown Jettons, they must also be returned.

TON Smart Contract Developer Must Control the Gas

In Solidity, gas is not much of a concern for contract developers. If the user provides too little gas, everything will be reverted as if nothing had happened (but the gas will not be returned). If they provide enough, the actual costs will automatically be calculated and deducted from their balance.

In TON, the situation is different:

  1. If there is not enough gas, the transaction will be partially executed.
  2. If there is too much gas, the excess must be returned. This is the developer’s responsibility.
  3. If a “group of contracts” exchanges messages, then control and calculation must be carried out in each message.

TON cannot automatically calculate the gas. The complete execution of the transaction with all its consequences can take a long time, and by the end, the user may not have enough toncoins in their wallet. The carry-value principle is used again here.

Tip #6: Calculate Gas and Check msg_value

According to our message flow diagram, we can estimate the cost of each handler in each of the scenarios and insert a check for the sufficiency of msg_value.

You can’t just demand with a margin, say 1 TON (the gas_limit on mainnet as of the date of writing) because this gas must be divided among the “consequences”. Let's say your contract sends three messages, then you can only send 0.33 TON to each. This means that they should “demand” less. It’s important to calculate the gas requirements of your whole contract carefully.

Things get more complicated if, during development, your code starts sending more messages. Gas requirements need to be rechecked and updated.

Tip #7: Return Gas Excesses Carefully

If excess gas is not returned to the sender, the funds will accumulate in your contracts over time. In principle, nothing terrible, this is just suboptimal practice. You can add a function for raking out excesses, but popular contracts like TON Jetton still return to the sender with the message op::excesses.

TON has a useful mechanism: SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE = 64. When using this mode in send_raw_message(), the rest of the gas will be forwarded further with the message (or back) to the new recipient. It is convenient if the message flow is linear: each message handler sends only one message. But there are cases when it is not recommended to use this mechanism:

  1. If there are no other non-linear handlers in your contract. storage_fee is deducted from the balance of the contract, and not from the incoming gas. This means that over time, storage_fee can eat up the entire balance because everything that comes in has to go out.
  2. If your contract emits events, i.e. sends a message to an external address. The cost of this action is deducted from the balance of the contract, and not from msg_value.
() emit_log_simple (int event_id, int query_id) impure inline {
    var msg = begin_cell()
        .store_uint (12, 4)             ;; ext_out_msg_info$11 addr$00
        .store_uint (1, 2)              ;; addr_extern$01
        .store_uint (256, 9)            ;; len:(## 9)
        .store_uint(event_id, 256);     ;; external_address:(bits len)
        .store_uint(0, 64 + 32 + 1 + 1) ;; lt, at, init, body
    send_raw_message(msg, SEND_MODE_REGULAR);
  1. If your contract attaches value when sending messages or uses SEND_MODE_PAY_FEES_SEPARETELY = 1. These actions deduct from the balance of the contract, which means that returning unused is "working at a loss."

In the indicated cases, a manual approximate calculation of the surplus is used:

int ton_balance_before_msg = my_ton_balance - msg_value;
int storage_fee = const::min_tons_for_storage - min(ton_balance_before_msg, const::min_tons_for_storage);
msg_value -= storage_fee + const::gas_consumption;

if(forward_ton_amount) {
    msg_value -= (forward_ton_amount + fwd_fee);

if (msg_value > 0) {    ;; there is still something to return

var msg = begin_cell()
    .store_uint(0x10, 6)

Remember, if the value of the contract balance runs out, the transaction will be partially executed, and this cannot be allowed.

TON Smart Contract Developer Must Manage the Storage

A typical message handler in TON follows this approach:

() handle_something(...) impure {
    (int total_supply, <a lot of vars>) = load_data();
    ... ;; do something, change data
    save_data(total_supply, <a lot of vars>);

Unfortunately, we are noticing a trend: <a lot of vars> is a real enumeration of all contract data fields. For example,

int total_supply, int swap_fee, int min_amount, int is_stopped, int user_count, int max_user_count, 
slice admin_address, slice router_address, slice jettonA_address, slice jettonA_wallet_address, 
int jettonA_balance, int jettonA_pending_balance, slice jettonB_address, slice jettonB_wallet_address, 
int jettonB_balance, int jettonB_pending_balance, int mining_amount, int datetime_amount, int minable_time, 
int half_life, int last_index, int last_mined, cell mining_rate_cell, cell user_info_dict, cell operation_gas, 
cell content, cell lp_wallet_code
) = load_data();

This approach has a number of disadvantages.

First, if you decide to add another field, say is_paused, then you need to update the load_data()/save_data() statements throughout the contract. And this is not only labor-intensive, but it also leads to hard-to-catch errors.

In a recent CertiK audit, we noticed the developer mixed up two arguments in places, and wrote:

save_data(total_supply, min_amount, swap_fee, ...

Without an external audit performed by a team of experts, finding such a bug is very difficult. The function was rarely used, and both confused parameters usually had a value of zero. You really have to know what you’re looking for to pick up on an error like this.

Secondly, there is "namespace pollution". Let's explain what the problem is with another example from an audit. In the middle of the function, the input parameter read:

int min_amount = in_msg_body~load_coins();

That is, there was a shadowing of the storage field by a local variable, and at the end of the function, this replaced value was stored in storage. The attacker had the opportunity to overwrite the state of the contract. The situation is aggravated by the fact that FunC allows the redeclaration of variables: “This is not a declaration, but just a compile-time insurance that min_amount has type int.”

And finally, parsing the entire storage and packing it back on every call to every function increases the gas cost.

Tip #8: Use Nested Storage

We recommend the following storage organization approach:

() handle_something(...) impure {
    (slice swap_data, cell liquidity_data, cell mining_data, cell discovery_data) = load_data();
    (int total_supply, int swap_fee, int min_amount, int is_stopped) = swap_data.parse_swap_data();
    swap_data = pack_swap_data(total_supply + lp_amount, swap_fee, min_amount, is_stopped);
    save_data(swap_data, liquidity_data, mining_data, discovery_data);

Storage consists of blocks of related data. If a parameter is used in each function, for example, is_paused, then it is provided immediately by load_data(). If a parameter group is needed only in one scenario, then it does not need to be unpacked, it will not have to be packed, and it will not clog the namespace.

If the storage structure requires changes (usually adding a new field), then much fewer edits will have to be made.

Moreover, the approach can be repeated. If there are 30 storage fields in our contract, then initially you can get four groups, and then get a couple of variables and another subgroup from the first group. The main thing is not to overdo it.

Note that since a cell can store up to 1023 bits of data and up to 4 references, you will have to split the data into different cells anyway.

Hierarchical data is one of the main features of TON, let's use it for its intended purpose.

Global variables can be used, especially at the prototyping stage, where it is not entirely obvious what will be stored in the contract.

global int var1;
global cell var2;
global slice var3;

() load_data() impure {
    var cs = get_data().begin_parse();
    var1 = cs~load_coins();
    var2 = cs~load_ref();
    var3 = cs~load_bits(512);

() save_data() impure {

That way, if you find out that you need another variable, you just add a new global variable and modify load_data() and save_data(). No changes throughout the contract are needed. However, since there is a limitation on the number of global variables (no more than 31), this pattern can be combined with the "nested storage" recommended above.

Global variables are also often more expensive than storing everything on the stack. This, however, depends on the number of stack permutations, so it is a good idea to prototype with globals and, when storage structure is completely clear, to switch to on stack variables with a "nested" pattern.

Tip #9: Use end_parse()

Use end_parse() wherever possible when reading data from storage and from the message payload. Since TON uses bit streams with variable data format, it’s helpful to ensure that you read as much as you write. This can save you an hour of debugging.

Tip #10: Use More Helper Functions, and Avoid Magic Numbers

This tip is not unique to FunC but it is especially relevant here. Write more wrappers, and helper functions, and declare more constants.

FunC initially has an incredible amount of magic numbers. If the developer does not make any effort to limit their usage, the result is something like this:

var msg = begin_cell()
    .store_uint(0xc4ff, 17)         ;; 0 11000100 0xff
    .store_uint(config_addr, 256)
    .store_grams(1 << 30)           ;; ~1 gram of value
    .store_uint(0, 107)
    .store_uint(0x4e565354, 32)
    .store_uint(query_id, 64)
send_raw_message(msg.end_cell(), 1);

This is code from a real project, and it scares off newbies.

Fortunately, in recent versions of FunC, a couple of standard declarations can make the code clearer and more expressive. For example:

const int SEND_MODE_REGULAR = 0;

builder store_msgbody_prefix_stateinit(builder b) inline {
    return b.store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1);

builder store_body_header(builder b, int op, int query_id) inline {
    return b.store_uint(op, 32).store_uint(query_id, 64);

() mint_tokens(slice to_address, cell jetton_wallet_code, int amount, cell master_msg) impure {
    cell state_init = calculate_jetton_wallet_state_init(to_address, my_address(), jetton_wallet_code);
    slice to_wallet_address = calculate_address_by_state_init(state_init);
    var msg = begin_cell()

    send_raw_message(msg.end_cell(), SEND_MODE_REGULAR);

We love expressive code!


In this article, we’ve gone through some of the unique features developers will encounter when programming smart contracts on TON using FunC. We’ve also provided 10 tips we’ve learned throughout the course of our audits that will make your experience as smooth as possible.

We’re excited to be supporting the TON ecosystem and working with the many great projects being built on this new and innovative blockchain platform.

For an audit of your smart contract code, or to consult with our team of auditors and security experts, don’t hesitate to get in touch at

CertiK is a pioneer in blockchain security, leveraging best-in-class AI technology and expert manual review to protect and monitor blockchain protocols and smart contracts. Founded in 2018 by professors from Yale University and Columbia University, CertiK secures the Web3 world by applying cutting-edge innovations from academia to enterprise, enabling mission-critical applications to scale safely.

+ Recent posts