diff --git a/bindings/tests/testenv/testenv.go b/bindings/tests/testenv/testenv.go index e1b740c3..3d2ff7fd 100644 --- a/bindings/tests/testenv/testenv.go +++ b/bindings/tests/testenv/testenv.go @@ -166,7 +166,9 @@ func (te *TestEnvironment) initialize() error { te.cleanup() return fmt.Errorf("timeout waiting for Sui node to be ready") default: - client := sui.NewSuiClient(fmt.Sprintf("http://localhost:%d", te.rpcPort)) + client := sui.NewSuiClientWithCustomClient(fmt.Sprintf("http://localhost:%d", te.rpcPort), &http.Client{ + Timeout: HTTPClientTimeout, + }) _, err := client.SuiGetChainIdentifier(context.Background()) if err == nil { return nil @@ -192,7 +194,9 @@ func (te *TestEnvironment) cleanup() { } func createClient() sui.ISuiAPI { - return sui.NewSuiClient(fmt.Sprintf("http://localhost:%d", instance.rpcPort)) + return sui.NewSuiClientWithCustomClient(fmt.Sprintf("http://localhost:%d", instance.rpcPort), &http.Client{ + Timeout: HTTPClientTimeout, + }) } func createAccount(t *testing.T) (utils.SuiSigner, error) { diff --git a/integration-tests/offramp/execute_test.go b/integration-tests/offramp/execute_test.go deleted file mode 100644 index 3769d625..00000000 --- a/integration-tests/offramp/execute_test.go +++ /dev/null @@ -1,687 +0,0 @@ -//go:build hidden - -package ccip_test - -import ( - "context" - "crypto/ed25519" - "encoding/hex" - "fmt" - "math/big" - "strings" - "testing" - "time" - - commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" - - "github.com/block-vision/sui-go-sdk/sui" - "github.com/block-vision/sui-go-sdk/transaction" - "github.com/holiman/uint256" - "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" - "github.com/smartcontractkit/chainlink-common/pkg/logger" - cld_ops "github.com/smartcontractkit/chainlink-deployments-framework/operations" - "github.com/smartcontractkit/chainlink-sui/bindings/bind" - mocklinktoken "github.com/smartcontractkit/chainlink-sui/bindings/packages/mock_link_token" - sui_ops "github.com/smartcontractkit/chainlink-sui/deployment/ops" - ccipops "github.com/smartcontractkit/chainlink-sui/deployment/ops/ccip" - lockreleaseops "github.com/smartcontractkit/chainlink-sui/deployment/ops/ccip_lock_release_token_pool" - offrampops "github.com/smartcontractkit/chainlink-sui/deployment/ops/ccip_offramp" - mcmsops "github.com/smartcontractkit/chainlink-sui/deployment/ops/mcms" - mocklinktokenops "github.com/smartcontractkit/chainlink-sui/deployment/ops/mock_link_token" - cwConfig "github.com/smartcontractkit/chainlink-sui/relayer/chainwriter/config" - "github.com/smartcontractkit/chainlink-sui/relayer/chainwriter/ptb/offramp" - "github.com/smartcontractkit/chainlink-sui/relayer/client" - rel "github.com/smartcontractkit/chainlink-sui/relayer/signer" - "github.com/smartcontractkit/chainlink-sui/relayer/testutils" - "github.com/stretchr/testify/require" -) - -const ( - evmReceiverAddress = "0x80226fc0ee2b096224eeac085bb9a8cba1146f7d" - SUI_CHAIN_SELECTOR = 2 - ETHEREUM_CHAIN_SELECTOR = 1 -) - -var ConfigDigest = []byte{ - 0x00, 0x0A, 0x2F, 0x1F, 0x37, 0xB0, 0x33, 0xCC, - 0xC4, 0x42, 0x8A, 0xB6, 0x5C, 0x35, 0x39, 0xC9, - 0x31, 0x5D, 0xBF, 0x88, 0x2D, 0x4B, 0xAB, 0x13, - 0xF1, 0xE7, 0xEF, 0xE7, 0xB3, 0xDD, 0xDC, 0x36, -} - -func setupClients(t *testing.T, lggr logger.Logger) (rel.SuiSigner, sui.ISuiAPI, ed25519.PrivateKey) { - t.Helper() - - // Start the node. - cmd, err := testutils.StartSuiNode(testutils.CLI) - require.NoError(t, err) - t.Cleanup(func() { - if cmd.Process != nil { - if perr := cmd.Process.Kill(); perr != nil { - t.Logf("Failed to kill process: %v", perr) - } - } - }) - - // Create the client. - client := sui.NewSuiClient(testutils.LocalUrl) - - // Generate key pair and create a signer. - pk, _, _, err := testutils.GenerateAccountKeyPair(t) - require.NoError(t, err) - signer := rel.NewPrivateKeySigner(pk) - - // Fund the account. - signerAddress, err := signer.GetAddress() - require.NoError(t, err) - for range 3 { - err = testutils.FundWithFaucet(lggr, "localnet", signerAddress) - require.NoError(t, err) - } - - return signer, client, pk -} - -func normalizeTo32Bytes(address string) []byte { - addressHex := address - if strings.HasPrefix(address, "0x") { - addressHex = address[2:] - } - addressBytesFull, _ := hex.DecodeString(addressHex) - addressBytes := addressBytesFull - if len(addressBytesFull) > 32 { - addressBytes = addressBytesFull[len(addressBytesFull)-32:] - } else if len(addressBytesFull) < 32 { - // pad left with zeros - padding := make([]byte, 32-len(addressBytesFull)) - addressBytes = append(padding, addressBytesFull...) - } - return addressBytes -} - -type EnvironmentSettings struct { - AccountAddress string - - // Deployment reports - MockLinkReport cld_ops.Report[cld_ops.EmptyInput, sui_ops.OpTxResult[mocklinktokenops.DeployMockLinkTokenObjects]] - CCIPReport cld_ops.SequenceReport[ccipops.DeployAndInitCCIPSeqInput, ccipops.DeployCCIPSeqOutput] - OffRampReport cld_ops.SequenceReport[offrampops.DeployAndInitCCIPOffRampSeqInput, offrampops.DeployCCIPOffRampSeqOutput] - TokenPoolReport cld_ops.SequenceReport[lockreleaseops.DeployAndInitLockReleaseTokenPoolInput, lockreleaseops.DeployLockReleaseTokenPoolOutput] - DummyReceiverReport cld_ops.SequenceReport[ccipops.DeployAndInitDummyReceiverInput, ccipops.DeployDummyReceiverSeqOutput] - - EthereumPoolAddress []byte - - // Signers - SignersAddrBytes [][]byte - Signer rel.SuiSigner - - // Public keys - PublicKeys [][]byte - - // Private keys - PrivateKeys []ed25519.PrivateKey - - // Client - Client sui.ISuiAPI -} - -func SetupOffRamp(t *testing.T, - report cld_ops.SequenceReport[ccipops.DeployAndInitCCIPSeqInput, ccipops.DeployCCIPSeqOutput], - deps sui_ops.OpTxDeps, - reportMCMs cld_ops.Report[cld_ops.EmptyInput, sui_ops.OpTxResult[mcmsops.DeployMCMSObjects]], - mockLinkReport cld_ops.Report[cld_ops.EmptyInput, sui_ops.OpTxResult[mocklinktokenops.DeployMockLinkTokenObjects]], - signerAddr string, - accountAddress string, - bundle cld_ops.Bundle, - lggr logger.Logger, - client sui.ISuiAPI, - privateKey ed25519.PrivateKey, -) cld_ops.SequenceReport[offrampops.DeployAndInitCCIPOffRampSeqInput, offrampops.DeployCCIPOffRampSeqOutput] { - t.Helper() - lggr.Debugw("Setting up off ramp") - - // Get the main account's public key first - keystoreInstance := testutils.NewTestKeystore(t) - _, publicKeyBytes := testutils.GetAccountAndKeyFromSui(keystoreInstance) - accountAddressBytes, err := hex.DecodeString(strings.TrimPrefix(accountAddress, "0x")) - require.NoError(t, err) - - // Create a dummy OnRamp address - OnRampAddress := make([]byte, 32) - OnRampAddress[31] = 20 - - // Declare all arrays - signerAddresses := make([]string, 0, 4) - signerAddrBytes := make([][]byte, 0, 4) - signerPublicKeys := make([][]byte, 0, 4) - signerPrivateKeys := make([]ed25519.PrivateKey, 0, 4) - - // add 3 generated signers - for range 3 { - pk, _, _, err := testutils.GenerateAccountKeyPair(t) - require.NoError(t, err) - - _signer := rel.NewPrivateKeySigner(pk) - - signerAddress, err := _signer.GetAddress() - require.NoError(t, err) - signerAddresses = append(signerAddresses, signerAddress) - - addrHex := strings.TrimPrefix(signerAddress, "0x") - addrBytes, err := hex.DecodeString(addrHex) - require.NoError(t, err) - signerAddrBytes = append(signerAddrBytes, addrBytes) - - // Extract the public key (32 bytes) for OCR3 - publicKey := pk.Public().(ed25519.PublicKey) - signerPublicKeys = append(signerPublicKeys, publicKey) - - signerPrivateKeys = append(signerPrivateKeys, pk) - } - - // the 4th signer is the account that will call the OffRamp - signerAddresses = append(signerAddresses, accountAddress) - //nolint:ineffassign,staticcheck - signerAddrBytes = append(signerAddrBytes, accountAddressBytes) - signerPublicKeys = append(signerPublicKeys, publicKeyBytes) - //nolint:ineffassign,staticcheck - signerPrivateKeys = append(signerPrivateKeys, privateKey) - - lggr.Infow("signer addresses", "signerAddresses", signerAddresses) - - seqOffRampInput := offrampops.DeployAndInitCCIPOffRampSeqInput{ - DeployCCIPOffRampInput: offrampops.DeployCCIPOffRampInput{ - CCIPPackageId: report.Output.CCIPPackageId, - MCMSPackageId: reportMCMs.Output.PackageId, - }, - InitializeOffRampInput: offrampops.InitializeOffRampInput{ - DestTransferCapId: report.Output.Objects.DestTransferCapObjectId, - FeeQuoterCapId: report.Output.Objects.FeeQuoterCapObjectId, - ChainSelector: SUI_CHAIN_SELECTOR, - PremissionExecThresholdSeconds: 10, - SourceChainSelectors: []uint64{ETHEREUM_CHAIN_SELECTOR}, - SourceChainsIsEnabled: []bool{true}, - SourceChainsIsRMNVerificationDisabled: []bool{true}, - SourceChainsOnRamp: [][]byte{OnRampAddress}, - }, - CommitOCR3Config: offrampops.SetOCR3ConfigInput{ - // Sample config digest - ConfigDigest: ConfigDigest, - OCRPluginType: byte(0), - BigF: byte(1), - IsSignatureVerificationEnabled: true, - Signers: signerPublicKeys, - Transmitters: signerAddresses, - }, - ExecutionOCR3Config: offrampops.SetOCR3ConfigInput{ - ConfigDigest: ConfigDigest, - OCRPluginType: byte(1), - BigF: byte(1), - IsSignatureVerificationEnabled: false, - Signers: signerPublicKeys, - Transmitters: signerAddresses, - }, - } - - offrampReport, err := cld_ops.ExecuteSequence(bundle, offrampops.DeployAndInitCCIPOffRampSequence, deps, seqOffRampInput) - require.NoError(t, err, "failed to deploy CCIP Package") - - lggr.Debugw("Offramp deployment report", "output", offrampReport.Output) - - return offrampReport -} - -func SetupTokenPool(t *testing.T, - report cld_ops.SequenceReport[ccipops.DeployAndInitCCIPSeqInput, ccipops.DeployCCIPSeqOutput], - deps sui_ops.OpTxDeps, - reportMCMs cld_ops.Report[cld_ops.EmptyInput, sui_ops.OpTxResult[mcmsops.DeployMCMSObjects]], - mockLinkReport cld_ops.Report[cld_ops.EmptyInput, sui_ops.OpTxResult[mocklinktokenops.DeployMockLinkTokenObjects]], - signerAddr string, - accountAddress string, - linkTokenType string, - ethereumPoolAddressString string, - remoteTokenAddressString string, - destChainSelector uint64, - bundle cld_ops.Bundle, - lggr logger.Logger, - client sui.ISuiAPI, -) cld_ops.SequenceReport[lockreleaseops.DeployAndInitLockReleaseTokenPoolInput, lockreleaseops.DeployLockReleaseTokenPoolOutput] { - t.Helper() - - lggr.Debugw("Setting up token pool") - // Create a context for the operation - c := context.Background() - ctx, cancel := context.WithCancel(c) - defer cancel() - - // Deploy and initialize the lock release token pool - seqLockReleaseDeployInput := lockreleaseops.DeployAndInitLockReleaseTokenPoolInput{ - LockReleaseTokenPoolDeployInput: lockreleaseops.LockReleaseTokenPoolDeployInput{ - CCIPPackageId: report.Output.CCIPPackageId, - MCMSAddress: reportMCMs.Output.PackageId, - MCMSOwnerAddress: accountAddress, - }, - // Initialization parameters - CoinObjectTypeArg: linkTokenType, - CCIPObjectRefObjectId: report.Output.Objects.CCIPObjectRefObjectId, - CoinMetadataObjectId: mockLinkReport.Output.Objects.CoinMetadataObjectId, - TreasuryCapObjectId: mockLinkReport.Output.Objects.TreasuryCapObjectId, - TokenPoolAdministrator: accountAddress, - Rebalancer: signerAddr, - - // Chain updates - adding the destination chain - RemoteChainSelectorsToRemove: []uint64{}, - RemoteChainSelectorsToAdd: []uint64{ETHEREUM_CHAIN_SELECTOR}, // Destination chain selector - RemotePoolAddressesToAdd: [][]string{{ethereumPoolAddressString}}, // 32-byte remote pool address - RemoteTokenAddressesToAdd: []string{remoteTokenAddressString}, // 32-byte remote token address - // Rate limiter configurations - RemoteChainSelectors: []uint64{ETHEREUM_CHAIN_SELECTOR}, // Destination chain selector - OutboundIsEnableds: []bool{false}, - OutboundCapacities: []uint64{1000000}, // 1M tokens capacity - OutboundRates: []uint64{100000}, // 100K tokens per time window - InboundIsEnableds: []bool{false}, - InboundCapacities: []uint64{1000000}, // 1M tokens capacity - InboundRates: []uint64{100000}, // 100K tokens per time window - } - - tokenPoolLockReleaseReport, err := cld_ops.ExecuteSequence(bundle, lockreleaseops.DeployAndInitLockReleaseTokenPoolSequence, deps, seqLockReleaseDeployInput) - require.NoError(t, err, "failed to deploy and initialize Lock Release Token Pool") - - lggr.Debugw("Token Pool Lock Release deployment report", "output", tokenPoolLockReleaseReport.Output) - - // Provide liquidity to the lock release token pool - // First, mint some LINK tokens using the LINK token contract - liquidityAmount := uint64(1000000) // 1M tokens for liquidity - - // Create LINK token contract instance - linkContract, err := mocklinktoken.NewMockLinkToken(mockLinkReport.Output.PackageId, client) - require.NoError(t, err, "failed to create LINK token contract") - - // Mint LINK tokens to the signer's address - mintTx, err := linkContract.MockLinkToken().Mint( - ctx, - deps.GetCallOpts(), - bind.Object{Id: mockLinkReport.Output.Objects.TreasuryCapObjectId}, - liquidityAmount, - ) - require.NoError(t, err, "failed to mint LINK tokens for liquidity") - - lggr.Debugw("Minted LINK tokens for liquidity", "amount", liquidityAmount, "txDigest", mintTx.Digest) - - // Find the minted coin object ID from the transaction - mintedCoinId, err := bind.FindCoinObjectIdFromTx(*mintTx, linkTokenType) - require.NoError(t, err, "failed to find minted coin object ID") - - lggr.Debugw("Minted coin ID", "mintedCoinId", mintedCoinId) - - // Provide the minted tokens as liquidity to the pool - provideLiquidityInput := lockreleaseops.LockReleaseTokenPoolProviderLiquidityInput{ - LockReleaseTokenPoolPackageId: tokenPoolLockReleaseReport.Output.LockReleaseTPPackageID, - StateObjectId: tokenPoolLockReleaseReport.Output.Objects.StateObjectId, - Coin: mintedCoinId, - CoinObjectTypeArg: linkTokenType, - } - - _, err = cld_ops.ExecuteOperation(bundle, lockreleaseops.LockReleaseTokenPoolProviderLiquidityOp, deps, provideLiquidityInput) - require.NoError(t, err, "failed to provide liquidity to Lock Release Token Pool") - - lggr.Debugw("Provided liquidity to Lock Release Token Pool", "amount", liquidityAmount) - - return tokenPoolLockReleaseReport -} - -func SetupDummyReceiver(t *testing.T, - report cld_ops.SequenceReport[ccipops.DeployAndInitCCIPSeqInput, ccipops.DeployCCIPSeqOutput], - deps sui_ops.OpTxDeps, - reportMCMs cld_ops.Report[cld_ops.EmptyInput, sui_ops.OpTxResult[mcmsops.DeployMCMSObjects]], - mockLinkReport cld_ops.Report[cld_ops.EmptyInput, sui_ops.OpTxResult[mocklinktokenops.DeployMockLinkTokenObjects]], - signerAddr string, - accountAddress string, - bundle cld_ops.Bundle, - lggr logger.Logger, -) cld_ops.SequenceReport[ccipops.DeployAndInitDummyReceiverInput, ccipops.DeployDummyReceiverSeqOutput] { - t.Helper() - - // Deploy and initialize the dummy receiver - dummyReceiverReport, err := cld_ops.ExecuteSequence(bundle, ccipops.DeployAndInitDummyReceiverSequence, deps, ccipops.DeployAndInitDummyReceiverInput{ - DeployDummyReceiverInput: ccipops.DeployDummyReceiverInput{ - CCIPPackageId: report.Output.CCIPPackageId, - McmsPackageId: reportMCMs.Output.PackageId, - McmsOwner: signerAddr, - }, - CCIPObjectRefObjectId: report.Output.Objects.CCIPObjectRefObjectId, - ReceiverStateParams: []string{"0x6"}, // the clock object id - }) - - require.NoError(t, err, "failed to deploy and initialize dummy receiver") - lggr.Debugw("Dummy receiver deployment report", "output", dummyReceiverReport.Output) - - return dummyReceiverReport -} - -func SetupFeeQuoterPrices(t *testing.T, - report cld_ops.SequenceReport[ccipops.DeployAndInitCCIPSeqInput, ccipops.DeployCCIPSeqOutput], - deps sui_ops.OpTxDeps, - reportMCMs cld_ops.Report[cld_ops.EmptyInput, sui_ops.OpTxResult[mcmsops.DeployMCMSObjects]], - mockLinkReport cld_ops.Report[cld_ops.EmptyInput, sui_ops.OpTxResult[mocklinktokenops.DeployMockLinkTokenObjects]], - signerAddr string, - destChainSelector uint64, - bundle cld_ops.Bundle, - lggr logger.Logger, -) { - t.Helper() - // **CRITICAL**: Set token prices in the fee quoter - // The fee quoter needs to know USD prices to calculate fees - // Set LINK token price to $5.00 USD (5 * 1e18 = 5e18) - linkTokenPrice := big.NewInt(0) - linkTokenPrice.SetString("5000000000000000000", 10) // $5.00 in 1e18 format - - // Set gas price for destination chain to 20 gwei (20 * 1e9 = 2e10) - gasPrice := big.NewInt(20000000000) // 20 gwei in wei - - updatePricesInput := ccipops.FeeQuoterUpdateTokenPricesInput{ - CCIPPackageId: report.Output.CCIPPackageId, - CCIPObjectRef: report.Output.Objects.CCIPObjectRefObjectId, - FeeQuoterCapId: report.Output.Objects.FeeQuoterCapObjectId, - SourceTokens: []string{mockLinkReport.Output.Objects.CoinMetadataObjectId}, - SourceUsdPerToken: []*big.Int{linkTokenPrice}, - GasDestChainSelectors: []uint64{destChainSelector}, - GasUsdPerUnitGas: []*big.Int{gasPrice}, - } - - _, err := cld_ops.ExecuteOperation(bundle, ccipops.FeeQuoterUpdateTokenPricesOp, deps, updatePricesInput) - require.NoError(t, err, "failed to update token prices in fee quoter") - - lggr.Infow("Updated token prices in fee quoter", "linkPrice", linkTokenPrice.String(), "gasPrice", gasPrice.String()) -} - -func SetupTestEnvironment(t *testing.T, localChainSelector uint64, destChainSelector uint64, keystoreInstance *testutils.TestKeystore) *EnvironmentSettings { - t.Helper() - - lggr := logger.Test(t) - lggr.Debugw("Starting Sui node") - - accountAddress, _ := testutils.GetAccountAndKeyFromSui(keystoreInstance) - - signer, client, privateKey := setupClients(t, lggr) - - // Create 20-byte Ethereum addresses for RMN Remote signers - ethAddr1, err := hex.DecodeString("8a1b2c3d4e5f60718293a4b5c6d7e8f901234567") - require.NoError(t, err, "failed to decode eth address 1") - ethAddr2, err := hex.DecodeString("7b8c9dab0c1d2e3f405162738495a6b7c8d9e0f1") - require.NoError(t, err, "failed to decode eth address 2") - ethAddr3, err := hex.DecodeString("1234567890abcdef1234567890abcdef12345678") - require.NoError(t, err, "failed to decode eth address 3") - - deps := sui_ops.OpTxDeps{ - Client: client, - Signer: signer, - GetCallOpts: func() *bind.CallOpts { - b := uint64(500_000_000) - return &bind.CallOpts{ - Signer: signer, - WaitForExecution: true, - GasBudget: &b, - } - }, - } - - reporter := cld_ops.NewMemoryReporter() - bundle := cld_ops.NewBundle( - context.Background, - logger.Test(t), - reporter, - ) - - // Deploy LINK - mockLinkReport, err := cld_ops.ExecuteOperation(bundle, mocklinktokenops.DeployMockLinkTokenOp, deps, cld_ops.EmptyInput{}) - require.NoError(t, err, "failed to deploy LINK token") - - configDigest, err := uint256.FromHex("0xe3b1c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") - require.NoError(t, err, "failed to convert config digest to uint256") - - // Deploy MCMs - reportMCMs, err := cld_ops.ExecuteOperation(bundle, mcmsops.DeployMCMSOp, deps, cld_ops.EmptyInput{}) - require.NoError(t, err, "failed to deploy MCMS Package") - lggr.Debugw("MCMS deployment report", "output", reportMCMs.Output) - - signerAddr, err := signer.GetAddress() - require.NoError(t, err) - - lggr.Debugw("LINK report", "output", mockLinkReport.Output) - - report, err := cld_ops.ExecuteSequence(bundle, ccipops.DeployAndInitCCIPSequence, deps, ccipops.DeployAndInitCCIPSeqInput{ - LinkTokenCoinMetadataObjectId: mockLinkReport.Output.Objects.CoinMetadataObjectId, - LocalChainSelector: localChainSelector, - DestChainSelector: destChainSelector, - DeployCCIPInput: ccipops.DeployCCIPInput{ - McmsPackageId: reportMCMs.Output.PackageId, - McmsOwner: signerAddr, - }, - MaxFeeJuelsPerMsg: "100000000", - TokenPriceStalenessThreshold: 60, - // Fee Quoter configuration - AddMinFeeUsdCents: []uint32{3000}, - AddMaxFeeUsdCents: []uint32{30000}, - AddDeciBps: []uint16{1000}, - AddDestGasOverhead: []uint32{1000000}, - AddDestBytesOverhead: []uint32{1000}, - AddIsEnabled: []bool{true}, - RemoveTokens: []string{}, - // Fee Quoter destination chain configuration - IsEnabled: true, - MaxNumberOfTokensPerMsg: 2, - MaxDataBytes: 2000, - MaxPerMsgGasLimit: 5000000, - DestGasOverhead: 1000000, - DestGasPerPayloadByteBase: byte(2), - DestGasPerPayloadByteHigh: byte(5), - DestGasPerPayloadByteThreshold: uint16(10), - DestDataAvailabilityOverheadGas: 300000, - DestGasPerDataAvailabilityByte: 4, - DestDataAvailabilityMultiplierBps: 1, - ChainFamilySelector: []byte{0x28, 0x12, 0xd5, 0x2c}, - EnforceOutOfOrder: false, - DefaultTokenFeeUsdCents: 3, - DefaultTokenDestGasOverhead: 100000, - DefaultTxGasLimit: 500000, - GasMultiplierWeiPerEth: 100, - GasPriceStalenessThreshold: 1000000000, - NetworkFeeUsdCents: 10, - // Premium multiplier updates - PremiumMultiplierWeiPerEth: []uint64{10}, - - RmnHomeContractConfigDigest: configDigest.Bytes(), - SignerOnchainPublicKeys: [][]byte{ethAddr1, ethAddr2, ethAddr3}, - NodeIndexes: []uint64{0, 1, 2}, - FSign: uint64(1), - }) - require.NoError(t, err, "failed to execute CCIP deploy sequence") - require.NotEmpty(t, report.Output.CCIPPackageId, "CCIP package ID should not be empty") - - offrampReport := SetupOffRamp(t, report, deps, reportMCMs, mockLinkReport, signerAddr, accountAddress, bundle, lggr, client, privateKey) - - linkTokenType := fmt.Sprintf("%s::mock_link_token::MOCK_LINK_TOKEN", mockLinkReport.Output.PackageId) - - ethereumPoolAddressString := evmReceiverAddress - remoteTokenAddressString := evmReceiverAddress - - tokenPoolReport := SetupTokenPool(t, report, deps, reportMCMs, mockLinkReport, - signerAddr, accountAddress, linkTokenType, ethereumPoolAddressString, remoteTokenAddressString, - destChainSelector, bundle, lggr, client, - ) - - //SetupFeeQuoterPrices(t, report, deps, reportMCMs, mockLinkReport, signerAddr, destChainSelector, bundle, lggr) - - dummyReceiverReport := SetupDummyReceiver(t, report, deps, reportMCMs, mockLinkReport, signerAddr, accountAddress, bundle, lggr) - - return &EnvironmentSettings{ - AccountAddress: accountAddress, - EthereumPoolAddress: []byte(ethereumPoolAddressString), - MockLinkReport: mockLinkReport, - CCIPReport: report, - OffRampReport: offrampReport, - TokenPoolReport: tokenPoolReport, - DummyReceiverReport: dummyReceiverReport, - Signer: signer, - Client: client, - } -} - -func TestExecuteOffRamp(t *testing.T) { - lggr := logger.Test(t) - env := SetupTestEnvironment(t, ETHEREUM_CHAIN_SELECTOR, SUI_CHAIN_SELECTOR, testutils.NewTestKeystore(t)) - - keystoreInstance := testutils.NewTestKeystore(t) - accountAddress, publicKeyBytes := testutils.GetAccountAndKeyFromSui(keystoreInstance) - - lggr.Infow("Environment settings", "env", env) - - offrampPackageId := env.OffRampReport.Output.CCIPOffRampPackageId - linkTokenPackageId := env.MockLinkReport.Output.Objects.CoinMetadataObjectId - - t.Run("TestTokenTransferWithArbitraryMessaging", func(t *testing.T) { - lggr.Infow("Testing Token Transfer with Arbitrary Messaging") - - ptb := transaction.NewTransaction() - ptb.SetSuiClient(env.Client.(*sui.Client)) - - receiverPackageId := env.DummyReceiverReport.Output.DummyReceiverPackageId - receiverModule := "dummy_receiver" - receiver := fmt.Sprintf("%s::%s::ccip_receive", receiverPackageId, receiverModule) - - tokenAmount := ccipocr3.BigInt{Int: big.NewInt(300)} - - rawContent := "Do or do not, there is no try." - msg := ccipocr3.Message{ - Receiver: []byte(receiver), - Data: []byte(rawContent), - } - - lggr.Infow("Message", "msg", msg) - - ptbClient, err := client.NewPTBClient(lggr, testutils.LocalUrl, nil, 10*time.Second, nil, 5, "WaitForLocalExecution") - require.NoError(t, err, "Failed to create PTB client for event querying") - - ctx := context.Background() - - addressMappings, err := offramp.GetOfframpAddressMappings( - ctx, - lggr, - ptbClient, - offrampPackageId, - publicKeyBytes, - ) - require.NoError(t, err, "failed to get offramp address mappings") - lggr.Infow("Offramp address mappings", "addressMappings", addressMappings) - - hexEncodedLinkPackageId, err := hex.DecodeString(strings.Replace(linkTokenPackageId, "0x", "", 1)) - require.NoError(t, err, "failed to decode link token package id") - - var messageIDBytes32 ccipocr3.Bytes32 - - execReport := ccipocr3.ExecuteReportInfo{ - AbstractReports: []ccipocr3.ExecutePluginReportSingleChain{ - { - SourceChainSelector: ETHEREUM_CHAIN_SELECTOR, - Messages: []ccipocr3.Message{ - { - Header: ccipocr3.RampMessageHeader{ - MessageID: messageIDBytes32, - SourceChainSelector: ccipocr3.ChainSelector(ETHEREUM_CHAIN_SELECTOR), - DestChainSelector: ccipocr3.ChainSelector(SUI_CHAIN_SELECTOR), - SequenceNumber: ccipocr3.SeqNum(uint64(1)), - Nonce: uint64(0), - }, - Receiver: []byte{}, // []byte(receiver), - Data: []byte(rawContent), - TokenAmounts: []ccipocr3.RampTokenAmount{ - { - SourcePoolAddress: env.EthereumPoolAddress, - DestTokenAddress: hexEncodedLinkPackageId, - Amount: tokenAmount, - ExtraData: []byte{}, - }, - }, - }, - }, - }, - }, - } - - offChainTokenData := [][]byte{ - make([]byte, 32), // config digest - 32 bytes - } - offChainTokenData[0] = []byte{ - 0x00, 0x0A, 0x2F, 0x1F, 0x37, 0xB0, 0x33, 0xCC, - 0xC4, 0x42, 0x8A, 0xB6, 0x5C, 0x35, 0x39, 0xC9, - 0x31, 0x5D, 0xBF, 0x88, 0x2D, 0x4B, 0xAB, 0x13, - 0xF1, 0xE7, 0xEF, 0xE7, 0xB3, 0xDD, 0xDC, 0x36, - } - proofs := [][]byte{} - - report := testutils.GetExecutionReportFromCCIP( - uint64(execReport.AbstractReports[0].SourceChainSelector), - execReport.AbstractReports[0].Messages[0], - offChainTokenData, - proofs, - uint32(1_000_000), - ) - - execReportBCSBytes, err := testutils.SerializeExecutionReport(report) - require.NoError(t, err, "failed to serialize execution report") - - reportContext := [][]byte{ - make([]byte, 32), // config digest - 32 bytes - make([]byte, 32), // epoch and round - 32 bytes - } - reportContext[0] = ConfigDigest - reportContext[1][0] = 0x022 - - args := cwConfig.Arguments{ - Args: map[string]interface{}{ - "ReportContext": reportContext, - "Report": execReportBCSBytes, - "Info": execReport, - }, - } - - err = offramp.BuildOffRampExecutePTB( - ctx, - lggr, - ptbClient, - ptb, - args, - accountAddress, - addressMappings, - ) - require.NoError(t, err, "failed to build offramp execute PTB") - lggr.Infow("Offramp execute PTB", "ptb", ptb) - - // Fund the account - for range 3 { - err = testutils.FundWithFaucet(lggr, "localnet", accountAddress) - require.NoError(t, err) - } - - _, txManager, _ := testutils.SetupClients(t, testutils.LocalUrl, keystoreInstance, lggr) - err = txManager.Start(ctx) - require.NoError(t, err) - - txID := "execute-offramp-test" - txMetadata := &commontypes.TxMeta{} - - _, err = txManager.EnqueuePTB(ctx, txID, txMetadata, publicKeyBytes, ptb, false) - require.NoError(t, err) - - require.Eventually(t, func() bool { - status, statusErr := txManager.GetTransactionStatus(ctx, txID) - if statusErr != nil { - lggr.Errorw("Failed to get transaction status", "error", statusErr) - return false - } - lggr.Debugw("Transaction status", "status", status) - return status == commontypes.Finalized - }, 10*time.Second, 1*time.Second, "Execute transaction final state not reached") - }) -} diff --git a/integration-tests/onramp/environment/setup.go b/integration-tests/onramp/environment/setup.go index 33dbee45..8b1a9a1b 100644 --- a/integration-tests/onramp/environment/setup.go +++ b/integration-tests/onramp/environment/setup.go @@ -62,40 +62,23 @@ type EnvironmentSettings struct { Client sui.ISuiAPI } -// SetupClients creates and configures Sui client and signer for testing. -// It generates a new key pair, creates a signer, and funds the signer address. -func SetupClients(t *testing.T, lggr logger.Logger) (rel.SuiSigner, sui.ISuiAPI) { +// BasicSetUp performs basic environment setup including account creation, client setup, +// and bundle initialization. This is the foundation for all test environments. +func BasicSetUp(t *testing.T, lggr logger.Logger, keystoreInstance *testutils.TestKeystore) (string, []byte, rel.SuiSigner, sui.ISuiAPI, sui_ops.OpTxDeps, cld_ops.Bundle) { t.Helper() - client := sui.NewSuiClient(testutils.LocalUrl) + gasLimit := int64(200000000000) + ptbClient, _, _, accountAddress, _, publicKeyBytes, _, _ := testutils.SetupTestEnv(t, context.Background(), lggr, gasLimit) - // Generate key pair and create a signer. + // Generate key pair and create a signer - use the same key for both signer and keystore pk, _, _, err := testutils.GenerateAccountKeyPair(t) require.NoError(t, err) signer := rel.NewPrivateKeySigner(pk) - - // Fund the signer for contract deployment - signerAddress, err := signer.GetAddress() + accountAddress, err = signer.GetAddress() require.NoError(t, err) - for range 3 { - err = testutils.FundWithFaucet(lggr, "localnet", signerAddress) - require.NoError(t, err) - } - - return signer, client -} - -// BasicSetUp performs basic environment setup including account creation, client setup, -// and bundle initialization. This is the foundation for all test environments. -func BasicSetUp(t *testing.T, lggr logger.Logger, keystoreInstance *testutils.TestKeystore) (string, []byte, rel.SuiSigner, sui.ISuiAPI, sui_ops.OpTxDeps, cld_ops.Bundle) { - t.Helper() - - accountAddress, publicKeyBytes := testutils.GetAccountAndKeyFromSui(keystoreInstance) - - signer, client := SetupClients(t, lggr) deps := sui_ops.OpTxDeps{ - Client: client, + Client: ptbClient.GetClient(), Signer: signer, GetCallOpts: func() *bind.CallOpts { b := uint64(500_000_000) @@ -114,7 +97,7 @@ func BasicSetUp(t *testing.T, lggr logger.Logger, keystoreInstance *testutils.Te reporter, ) - return accountAddress, publicKeyBytes, signer, client, deps, bundle + return accountAddress, publicKeyBytes, signer, ptbClient.GetClient(), deps, bundle } // UpdatePrices sets token prices in the fee quoter contract. diff --git a/knowledge_base/sui_open_ai_deep_research_report.md b/knowledge_base/sui_open_ai_deep_research_report.md deleted file mode 100644 index b414a3d6..00000000 --- a/knowledge_base/sui_open_ai_deep_research_report.md +++ /dev/null @@ -1,333 +0,0 @@ -# Integrating the Sui Blockchain into Your Application: A Technical Guide - -- [Integrating the Sui Blockchain into Your Application: A Technical Guide](#integrating-the-sui-blockchain-into-your-application-a-technical-guide) - - [1. Core Data Structures in Sui: Objects and Move Contracts](#1-core-data-structures-in-sui-objects-and-move-contracts) - - [2. Transaction Lifecycle: From Submission to Finality](#2-transaction-lifecycle-from-submission-to-finality) - - [3. Gas Mechanics: Fees, Payment, and Execution Impact](#3-gas-mechanics-fees-payment-and-execution-impact) - - [4. Golang SDK Support for Sui (Community Library)](#4-golang-sdk-support-for-sui-community-library) - - [4.1 Connecting to Sui and Key Management](#41-connecting-to-sui-and-key-management) - - [4.2 Submitting a Transaction (e.g. Object Transfer)](#42-submitting-a-transaction-eg-object-transfer) - - [4.3 Querying Blockchain State](#43-querying-blockchain-state) - - [4.4 Calling Move Contract Functions](#44-calling-move-contract-functions) - - [5. Sui vs Ethereum: Key Differences and Migration Considerations](#5-sui-vs-ethereum-key-differences-and-migration-considerations) - - [6. Developer Environment Setup (Sui CLI, Local Network, and SDKs)](#6-developer-environment-setup-sui-cli-local-network-and-sdks) - - [7. Sui’s Validator \& Consensus Mechanism (vs Ethereum)](#7-suis-validator--consensus-mechanism-vs-ethereum) - - [8. Common Integration Challenges and Best Practices](#8-common-integration-challenges-and-best-practices) - - [**References** (Documentation and Sources)](#references-documentation-and-sources) - - [Original Prompt](#original-prompt) - - [Suggestions to the model:](#suggestions-to-the-model) - -**Introduction:** -Sui is a high-performance Layer-1 blockchain designed with an **object-centric data model** and the **Move** programming language. Unlike account-based chains (e.g. Ethereum), Sui treats every on-chain asset or contract as an _object_ identified by a unique ID ([Object Model | Sui Documentation](https://docs.sui.io/concepts/object-model#:~:text=The%20basic%20unit%20of%20storage,objects%20on%20the%20Sui%20network)). This guide provides a technical, hands-on overview of Sui integration – from core data structures and transaction flow to Go SDK usage, and contrasts with Ethereum’s approach. We’ll also cover setting up a development environment, understanding Sui’s consensus, and tackling common integration challenges. Code examples use the popular community Sui Go SDK to demonstrate key operations. - -## 1. Core Data Structures in Sui: Objects and Move Contracts - -**Object-Centric Model:** The basic unit of storage on Sui is the **object**, not an account balance. Every user asset, token, or NFT is a Move-defined object with its own state and unique ID ([Object Model | Sui Documentation](https://docs.sui.io/concepts/object-model#:~:text=The%20basic%20unit%20of%20storage,objects%20on%20the%20Sui%20network)) ([Object Model | Sui Documentation](https://docs.sui.io/concepts/object-model#:~:text=Object%20metadata)). Even smart contracts themselves are objects (specifically, **Move packages**). A _Sui Move Package_ is an on-chain module bundle (immutable after publication) that defines logic for creating and manipulating Move objects ([Object Model | Sui Documentation](https://docs.sui.io/concepts/object-model#:~:text=stores%2C%20Sui%27s%20storage%20is%20centered,objects%20on%20the%20Sui%20network)) ([Object Model | Sui Documentation](https://docs.sui.io/concepts/object-model#:~:text=published%20to%20Sui.%20,object%20structs)). Each object carries metadata including: a globally unique 32-byte **Object ID** (derived from the creating transaction’s digest), a version number incremented on each mutation, the last transaction digest that modified it, and an **owner** field designating how the object is owned (by an address, another object, shared, or immutable) ([Object Model | Sui Documentation](https://docs.sui.io/concepts/object-model#:~:text=Each%20Sui%20object%20has%20the,following%20metadata)). This model is a shift from Ethereum’s large account storage mappings – instead, _each asset is a first-class entity_ addressable by ID, enabling fine-grained tracking and ownership controls. - -**Move Programming Language:** Sui uses **Move**, a Rust-based smart contract language originally from Facebook’s Diem project, adapted for Sui’s object model. Move is _resource-oriented_, making assets and permissions explicit in types. On Sui, Move modules define struct types for objects and functions to mutate or transfer them. Sui’s version of Move adds custom rules to optimize for scalability – for example, global storage operations (`move_to`, `move_from`) are removed in favor of passing objects into transactions, which allows static analysis of dependencies for parallel execution ([Move Concepts | Sui Documentation](https://docs.sui.io/concepts/sui-move-concepts#:~:text=Object)) ([Move Concepts | Sui Documentation](https://docs.sui.io/concepts/sui-move-concepts#:~:text=In%20Move%20on%20Diem%2C%20global,move_from)). In practice, this means a Sui Move smart contract function (often an **entry function**) will take specific object references as inputs, rather than reading arbitrary global state. Move’s safety (linear types for resources) prevents double-spending by design, and Sui extends this with strict object ownership rules. Developers use Move to implement asset logic (coins, NFTs, etc.) as _resources_ that can only be created, moved, or destroyed according to the contract’s rules ([Move Concepts | Sui Documentation](https://docs.sui.io/concepts/sui-move-concepts#:~:text=Move%20is%20an%20open%20source,optimization%20on%20the%20Sui%20blockchain)) ([Move Concepts | Sui Documentation](https://docs.sui.io/concepts/sui-move-concepts#:~:text=,take%20object%20references%20as%20input)). In summary, Sui’s core data model is one of **objects and modules** – objects encapsulate data (similar to Ethereum contract storage, but per-object), and Move modules enforce safe manipulation of those objects. - -## 2. Transaction Lifecycle: From Submission to Finality - -Any Sui transaction (whether a simple coin transfer or a complex contract call) goes through a multi-stage lifecycle before it’s finalized on-chain ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=At%20a%20high%20level%2C%20the,transaction%20on%20the%20Sui%20blockchain)). At a high level, the process consists of **transaction creation**, **certification (consensus)**, **execution**, and finalization via checkpoints. The diagram below illustrates the flow: - -([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle)) _Figure: Sui transaction lifecycle (1) User creates and signs a transaction. (2) Validators check the transaction and lock any involved objects. (3) The client collects signatures to form a certificate. (4) If needed, consensus orders the transaction (for shared objects). (5) Validators execute the certificate. (6) An effects certificate is formed as proof of execution. (7) The transaction is included in a checkpoint for finality ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=1,they%20own%20and%20shared%20objects)) ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=validators%2C%20who%20check%20its%20validity,operated%20by%20the%20Sui%20validators))._ - -**Transaction Submission:** A user begins by assembling a transaction containing the intended operations (e.g. “transfer this object to address X” or “call function Y in module Z”), along with a designated **gas payment object** (a SUI coin they own to cover fees) ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=example%2C%20imagine%20you%20want%20to,it%20must%20also%20sign%20it)) ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=,in%20the%20transaction%27s%20gas%20budget)). The transaction is signed with the user’s key, producing a signed transaction data structure. The client (wallet or application) then submits this signed transaction to a Sui **full node** or directly to validators via RPC ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=Certification%20happens%20after%20a%20transaction,must%20pass%20the%20following%20checks)). - -**Validator Checks and Certification:** Upon receiving the transaction, each Sui **validator** performs a series of _validity and safety checks_. These include verifying the user’s signature, ensuring the sender actually owns all the _owned input objects_ referenced (to prevent sending an object you don’t own), confirming any _shared objects_ exist, and checking that the gas coin is a valid `Coin` with a balance >= the gas budget ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=Full%20node%20cannot%20certify%20the,must%20pass%20the%20following%20checks)) ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=objects%20the%20transaction%20uses,in%20the%20transaction%27s%20gas%20budget)). If all checks pass, the validator _locks_ each owned input object for this transaction (preventing double-spend of those objects while the tx is in flight) ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=as%20specified%20in%20the%20transaction%27s,gas%20budget)). The validator then signs an attestation of the transaction’s validity. The client (or full node) needs to collect signatures from a supermajority (>= 2/3 of voting power) of validators to _certify_ the transaction ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=NFT%20to%20only%20one%20friend,NFT%20to%20all%20your%20friends)) ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=After%20the%20Full%20node%20collects,has%20formed%20a%20transaction%20certificate)). Once enough signatures are gathered, they are aggregated into a **transaction certificate** – essentially a proof that the network agrees this transaction is valid and locked its inputs. - -**Execution Paths – Fast vs Consensus:** The client now submits the certificate back to the validators for execution ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=Full%20nodes%20send%20transactions%20that,spend%20any%20objects)). Sui can execute many transactions _in parallel_, and it distinguishes between two cases: - -- **Transactions with only owned objects** (no shared objects): These can take the _fast path_. Since the inputs are exclusively owned by the sender and already locked, no further global ordering is needed. Each validator can immediately execute the transaction from the certificate without running it through the consensus engine ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=Then%2C%20each%20validator%20does%20the,based%20on%20whether%20the%20transaction)). In Sui’s design, such independent transactions bypass consensus and are processed quickly, as there’s no risk of conflict with other transactions (the object locks have ensured exclusivity) ([Sui Blockchain: A Deep Dive](https://stakin.com/blog/sui-blockchain-a-deep-dive#:~:text=Transactions%20that%20don%27t%20involve%20mutably,the%20capacity%20for%20parallel%20processing)) ([What is Sui Blockchain - Sui Ecosystem Explained](https://everstake.one/blog/what-is-sui-blockchain-sui-ecosystem-explained#:~:text=Sui%20consensus%20mechanism)). -- **Transactions involving shared objects**: Shared objects are global state that multiple parties could access (analogous to a contract that anyone can call). These transactions _require consensus ordering_, since their effects could conflict if reordered. In this case, the certificate is sent into Sui’s **DAG-based consensus** system to be sequenced with respect to other transactions touching the same shared object ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=Then%2C%20each%20validator%20does%20the,based%20on%20whether%20the%20transaction)). Sui uses a Byzantine Fault Tolerant protocol (Narwhal + Bullshark, detailed later) to order these transactions consistently across validators. Once the consensus layer determines an order, validators then execute the transactions in that order. - -**Result and Finality:** After execution, validators output the transaction’s **effects** – the new states of any modified or created objects, the gas used, events emitted, etc. Validators sign the effects, and a supermajority of signatures on the effects yields an **effects certificate** ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=An%20effects%20certificate%20is%20a,guarantee%20of%20transaction%20finality)) ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=2,and%20commit%20to%20the%20effects)). An effects certificate is a _guarantee of finality_ ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=An%20effects%20certificate%20is%20a,guarantee%20of%20transaction%20finality)): it means >=2/3 validators executed the tx and agreed on the outcome. At this point, the transaction is irreversible; its updated objects are now part of the global state and unlocked for future use (with updated versions) ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=match%20at%20L227%20epoch%2C%20no,objects%20during%20the%20same%20epoch)) ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=2,and%20commit%20to%20the%20effects)). Sui periodically forms **checkpoints** that include batches of certificates (both fast-path and consensus transactions) to provide a global ordering and support epoch changes ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=5,those%20that%20contain%20shared%20objects)). For most applications, obtaining the transaction’s effects (or inclusion in a checkpoint) is sufficient to consider it final. - -**Notably**, Sui’s approach differs from Ethereum’s block-based lifecycle. There is no mempool competition on gas price or single leader proposing a block of transactions. Instead, each transaction moves independently through the above steps, and non-conflicting transactions can be processed concurrently. Simple transfers (only owned objects) are confirmed extremely fast – after signatures are collected, they execute without waiting for global consensus ([Sui Blockchain: A Deep Dive](https://stakin.com/blog/sui-blockchain-a-deep-dive#:~:text=Transactions%20that%20don%27t%20involve%20mutably,the%20capacity%20for%20parallel%20processing)) ([Sui Blockchain: A Deep Dive](https://stakin.com/blog/sui-blockchain-a-deep-dive#:~:text=Unlike%20these%20traditional%20models%2C%20Sui%27s,friendly%20platform)). More complex transactions pay a slight latency cost to be ordered via consensus. This design yields low latency for most operations while still preserving strong consistency for any that need it. In practice, a basic transfer on Sui might reach finality in under a second under normal network conditions, whereas Ethereum requires block confirmation plus finality (~12+ seconds). The trade-off is that the client (or full node) shoulders more responsibility – assembling certificates and retrying if needed – as opposed to Ethereum where miners/validators handle ordering automatically. Sui provides robust libraries and node software to handle this certificate workflow for developers. - -## 3. Gas Mechanics: Fees, Payment, and Execution Impact - -**Gas in Sui = Compute + Storage:** Sui’s gas model is designed to keep fees low and predictable ([Deep Dive: Sui Reference Gas Price - Figment](https://figment.io/insights/deep-dive-sui-reference-gas-price/#:~:text=Deep%20Dive%3A%20Sui%20Reference%20Gas,the%20start%20of%20each%20epoch)) ([Gas fees on Sui - Tusky](https://tusky.io/blog/gas-fees-on-sui#:~:text=Gas%20fees%20on%20Sui%20,low%20transaction%20fees%2C%20and)). Every transaction pays for two components: the computational cost of executing the transaction, and the cost of storing any new data on-chain. Formally: - -> **`total_gas_fees = (computation_units * reference_gas_price) + (storage_units * storage_price)`** ([Gas in Sui | Sui Documentation](https://docs.sui.io/concepts/tokenomics/gas-in-sui#:~:text=A%20Sui%20transaction%20must%20pay,pays%20the%20following%20gas%20fees)) - -Here, _computation units_ measure the relative CPU/memory work of the transaction, and _storage units_ measure the size of new objects created or modified ([Gas in Sui | Sui Documentation](https://docs.sui.io/concepts/tokenomics/gas-in-sui#:~:text=Computation%20units)) ([Gas in Sui | Sui Documentation](https://docs.sui.io/concepts/tokenomics/gas-in-sui#:~:text=Different%20Sui%20transactions%20require%20varying,transactions%20require%20more%20computation%20units)). These are multiplied by their respective prices: the **reference gas price** (a network-wide SUI per unit rate updated each epoch by validators) ([Gas in Sui | Sui Documentation](https://docs.sui.io/concepts/tokenomics/gas-in-sui#:~:text=Gas%20prices)), and a long-term **storage price**. The product gives the fee in SUI token micro-units (called “Mist”). Sui uses a coarse-grained metering approach where transactions fall into buckets of computation cost (e.g. 1,000 units for simple txns, up to 5,000,000 units max) ([Gas in Sui | Sui Documentation](https://docs.sui.io/concepts/tokenomics/gas-in-sui#:~:text=Importantly%2C%20though%2C%20Sui%20computation%20gas,transaction%20requires%20more%20computation%20units)). This reduces the need for “gas golfing” micro-optimizations – many similar transactions cost exactly the same, simplifying developer planning ([Gas in Sui | Sui Documentation](https://docs.sui.io/concepts/tokenomics/gas-in-sui#:~:text=Importantly%2C%20though%2C%20Sui%20computation%20gas,transaction%20requires%20more%20computation%20units)) ([Gas in Sui | Sui Documentation](https://docs.sui.io/concepts/tokenomics/gas-in-sui#:~:text=Using%20coarse%20bucketing%20accomplishes%20two,important%20goals)). - -**Storage Fees and Rebate:** When a transaction creates or mutates objects, it incurs a storage fee (size * storage_price) that is deposited into a storage fund for future validator compensation ([What is Sui Blockchain - Sui Ecosystem Explained](https://everstake.one/blog/what-is-sui-blockchain-sui-ecosystem-explained#:~:text=Image%3A%20Sui%20Storage%20Fund)) ([What is Sui Blockchain - Sui Ecosystem Explained](https://everstake.one/blog/what-is-sui-blockchain-sui-ecosystem-explained#:~:text=Sui%20consensus%20mechanism)). However, if a transaction *deletes\* or frees up storage (for example, by destroying an object), Sui will credit a **storage rebate** back to the user. The **net fee** paid = compute fee + storage fee – rebate ([Gas in Sui | Sui Documentation](https://docs.sui.io/concepts/tokenomics/gas-in-sui#:~:text=Finally%2C%20Sui%20storage%20mechanics%20provide,rebates%20associated%20with%20data%20deletion)). This means if you delete large objects, you can earn back some SUI, encouraging cleanup of state. In practice, the user’s gas coin is charged the net gas fee at the end of execution. Any unused portion of the upfront gas budget is refunded (similar to Ethereum’s gas refund model), minus a small amount in certain failure cases. - -**Gas Budget and Payment:** Users specify a **gas budget** in each transaction, denominated in Mist (1 SUI = 10^9 Mist). The gas budget is the maximum amount of gas they are willing to pay for that transaction. For the transaction to execute, the budget must be at least the expected required gas. Formally, _gas_budget_ must be ≥ max{computation_fee, total_fee} ([Gas in Sui | Sui Documentation](https://docs.sui.io/concepts/tokenomics/gas-in-sui#:~:text=The%20gas%20budget%20for%20a,transactions%20are%20successfully%20executed%20if)). If the budget is too low, the transaction will fail upfront and still charge a portion of the budget (e.g. if it didn’t even cover the computation cost, the entire budget may be taken as a penalty) ([Gas in Sui | Sui Documentation](https://docs.sui.io/concepts/tokenomics/gas-in-sui#:~:text=If%20the%20gas%20budget%20does,is%20sufficient%20for%20covering)). Sui enforces a minimum gas budget of 2,000 Mist (0.000002 SUI) to ensure every transaction pays at least a tiny fee ([Gas in Sui | Sui Documentation](https://docs.sui.io/concepts/tokenomics/gas-in-sui#:~:text=storage%20fees%20,total%20gas%20fees%20you%20pay)). There’s also a protocol-defined max budget (e.g. 50 SUI) to prevent overflow or abuse ([Gas in Sui | Sui Documentation](https://docs.sui.io/concepts/tokenomics/gas-in-sui#:~:text=match%20at%20L161%20minimal%20gas,for%20denial%20of%20service%20attacks)). - -Importantly, the user _must pay gas with an owned SUI coin object_. In Sui, the native token SUI itself lives in the form of coin objects (each coin object has a value field). To cover gas, the transaction includes one of the user’s coin objects as the **gas payment object**, and that coin’s value is debited by the fee. The node will verify that this coin’s balance ≥ gas budget ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=objects%20the%20transaction%20uses,in%20the%20transaction%27s%20gas%20budget)). If the fee is less than the coin’s value, the coin may be split, with the fee portion burned (or moved to validators) and the remainder returned as change (a new coin object). If the fee equals the coin’s value, the coin might be fully consumed. This design means users manage potentially many coin objects (akin to UTXOs), but Sui’s runtime can automatically merge/split coins when needed. For developers, it’s crucial to ensure the chosen gas coin is **current and not already used** by another pending transaction. The Sui RPC can suggest a fresh gas coin or you can track your coin object IDs after each use. Using an outdated coin reference (e.g., one with an older version) will cause the transaction to be rejected as invalid. In fact, a common integration challenge is handling the **object versioning** of gas coins: a full node with stale state might pick the wrong coin version, leading to a client error or “double spend” attempt ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=choose%20a%20gas%20object%20for,on%20Full%20node%20before%20returning)). Best practice is to query the latest state for the gas coin (or use a wallet service) before sending a transaction. - -In summary, Sui’s gas mechanism ensures users pay a predictable fee closely tied to the resources they consume. There are no complex gas auctions; all users typically pay the same reference price per unit, which validators update only at epoch boundaries for stability ([Gas in Sui | Sui Documentation](https://docs.sui.io/concepts/tokenomics/gas-in-sui#:~:text=Gas%20prices)). This results in low, stable fees (often fractions of a cent ([Exploring Sui: The Layer-1 Ready for Mass Adoption | VanEck](https://www.vaneck.com/dk/en/blog/digital-assets/exploring-sui-the-layer-1-ready-for-mass-adoption/#:~:text=,developers%20to%20create%20simple%20user)) ([Exploring Sui: The Layer-1 Ready for Mass Adoption | VanEck](https://www.vaneck.com/dk/en/blog/digital-assets/exploring-sui-the-layer-1-ready-for-mass-adoption/#:~:text=second%3B%20can%20potentially%20scale%20to,due%20to%20Sui%E2%80%99s%20application%20guardrails))). For developers, it means budgeting for both compute and storage costs and managing gas coin objects, but it avoids wild fee swings and provides the opportunity to recoup storage costs via rebates. - -## 4. Golang SDK Support for Sui (Community Library) - -Since Mysten Labs (Sui’s creator) primarily provides Rust and TypeScript SDKs, the Sui community has stepped up to create unofficial Go libraries. The **most popular Golang SDK for Sui** at present is the open-source **Sui-Go-SDK** by BlockVision ([GitHub - block-vision/sui-go-sdk: Go language SDK for @MystenLabs Sui](https://github.com/block-vision/sui-go-sdk#:~:text=Overview)). This library wraps all Sui JSON-RPC methods and provides convenient Go APIs for common tasks, such as querying objects, transferring tokens, calling Move contract functions, and even listening to events via websockets ([GitHub - block-vision/sui-go-sdk: Go language SDK for @MystenLabs Sui](https://github.com/block-vision/sui-go-sdk#:~:text=The%20Sui,Powered%20by%20SuiVision%20team)) ([GitHub - block-vision/sui-go-sdk: Go language SDK for @MystenLabs Sui](https://github.com/block-vision/sui-go-sdk#:~:text=,events%20or%20transactions%20via%20websockets)). It’s a production-ready library used in community projects (the ComingChat team also maintains a similar Go SDK, but BlockVision’s has gained significant traction with Go developers, given its breadth of features and active maintenance – at ~140 stars on GitHub, it’s currently the most popular). - -Using the Sui-Go-SDK, Go developers can integrate Sui with minimal friction. Below, we demonstrate **hands-on examples** of key integration tasks: submitting a transaction, querying chain state, and interacting with a Move contract. Ensure you have Go 1.20+ and have run `go get github.com/block-vision/sui-go-sdk` to install the library ([GitHub - block-vision/sui-go-sdk: Go language SDK for @MystenLabs Sui](https://github.com/block-vision/sui-go-sdk#:~:text=Install)). - -### 4.1 Connecting to Sui and Key Management - -First, set up a Sui RPC client and account key. The SDK can connect to any Sui endpoint (Mainnet, Testnet, local) and includes a simple wallet for keypair management: - -```go -import ( - "context" - "fmt" - "github.com/block-vision/sui-go-sdk/constant" - "github.com/block-vision/sui-go-sdk/sui" - "github.com/block-vision/sui-go-sdk/signer" -) - -func main() { - // Connect to Sui network (here using BlockVision's public Testnet RPC) - cli := sui.NewSuiClient(constant.BvTestnetEndpoint) - - // Load an account from a mnemonic (or use NewSigner with a private key) - signerAccount, err := signer.NewSignertWithMnemonic("your 12-24 word mnemonic phrase here") - if err != nil { - panic(err) - } - fmt.Println("Loaded address:", signerAccount.Address) -} -``` - -When using this in production, you’d retrieve the mnemonic or key from a secure source (never hard-code secrets). The SDK supports both Ed25519 and Secp256k1 keys if needed. Now `cli` is ready to call Sui RPCs, and `signerAccount` holds your address and private key for signing transactions. - -### 4.2 Submitting a Transaction (e.g. Object Transfer) - -To send a transaction, you typically build a _transaction block_, then sign and execute it. For a simple example, let’s transfer an object (perhaps an NFT) we own to another address: - -```go -objID := "0x99b51302b66bd65b070cdb549b86e4b9aa7370cfddc70211c2b5a478140c7999" // object to transfer -recvAddr := "0xaf9f4d20c205f26051a7e1758601c4c47a9f99df3f9823f70926c17c80882d36" // recipient address -gasObj := "0xc699c6014da947778fe5f740b2e9caf905ca31fb4c81e346f467ae126e3c03f1" // one of our SUI coins for gas - -// 1. Build the transfer transaction block -txParams := models.TransferObjectRequest{ - Signer: signerAccount.Address, - ObjectId: objID, - Recipient: recvAddr, - Gas: &gasObj, - GasBudget: "100000000", // gas budget in Mist (100 million Mist = 0.1 SUI) -} -txBlock, err := cli.TransferObject(ctx, txParams) -if err != nil { - log.Fatal("Transaction build failed:", err) -} - -// 2. Sign and execute the transaction block -execParams := models.SignAndExecuteTransactionBlockRequest{ - TxnMetaData: txBlock, // the unsigned transaction data - PriKey: signerAccount.PriKey, - Options: models.SuiTransactionBlockOptions{ ShowEffects: true }, - RequestType: "WaitForLocalExecution", -} -result, err := cli.SignAndExecuteTransactionBlock(ctx, execParams) -if err != nil { - log.Fatal("Execution failed:", err) -} -utils.PrettyPrint(result) -``` - -In this code, `TransferObject` crafts a transaction to transfer the object with ID `objID` to `recvAddr` ([GitHub - block-vision/sui-go-sdk: Go language SDK for @MystenLabs Sui](https://github.com/block-vision/sui-go-sdk#:~:text=gasObj%20%3A%3D%20)) ([GitHub - block-vision/sui-go-sdk: Go language SDK for @MystenLabs Sui](https://github.com/block-vision/sui-go-sdk#:~:text=%2F%2F%20see%20the%20successful%20transaction,ShowInput%3A%20%20%20%20true)). We specify our `Signer` address and a `GasBudget`. We also pass the ID of a gas coin (`gasObj`) – if omitted, the SDK will try to auto-select one. The SDK returns an unsigned transaction block (essentially the payload that needs signing). We then call `SignAndExecuteTransactionBlock`, providing our private key and telling it to wait for local execution (this flags the RPC to return the result after execution is done). The end result (`result`) includes the transaction effects, which you can inspect to verify success and see the new object version, etc. Under the hood, the SDK handled getting the needed validator signatures and ensured the transaction reached finality (we requested `WaitForLocalExecution`, which waits for the transaction to be executed locally by a validator). In a production app, you might choose `WaitForEffectsCert` or handle retries yourself, but the provided options simplify confirmation. - -**Note:** Always ensure the `GasBudget` is sufficient. In the above, we used `100000000` Mist = 0.1 SUI, which is plenty for a simple transfer (actual cost might be ~0.001 SUI). Any unused gas will be refunded in the gas coin. - -### 4.3 Querying Blockchain State - -The Go SDK also makes it easy to read on-chain data via Sui’s JSON-RPC. For example, to fetch the details of an object by its ID: - -```go -objID := "0xeeb964d1e640219c8ddb791cc8548f3242a3392b143ff47484a3753291cad898" -resp, err := cli.SuiGetObject(ctx, models.SuiGetObjectRequest{ - ObjectId: objID, - Options: models.SuiObjectDataOptions{ - ShowType: true, - ShowOwner: true, - ShowPreviousTransaction: true, - ShowContent: true, - }, -}) -if err != nil { - log.Fatal(err) -} -utils.PrettyPrint(resp) -``` - -This will return the object’s type, owner, last tx digest, and content (Move struct fields) ([GitHub - block-vision/sui-go-sdk: Go language SDK for @MystenLabs Sui](https://github.com/block-vision/sui-go-sdk#:~:text=rsp%2C%20err%20%3A%3D%20cli.SuiGetObject%28ctx%2C%20models.SuiGetObjectRequest,true)) ([GitHub - block-vision/sui-go-sdk: Go language SDK for @MystenLabs Sui](https://github.com/block-vision/sui-go-sdk#:~:text=ShowPreviousTransaction%3A%20true%2C%20ShowStorageRebate%3A%20%20,)). You can similarly get transactions by digest, or use more advanced queries. For instance, to get all coin balances of an address: - -```go -ownerAddr := "0xb7f98d327f19f674347e1e40641408253142d6e7e5093a7c96eda8cdfd7d9bb5" -balResp, err := cli.SuiXGetAllBalance(ctx, models.SuiXGetAllBalanceRequest{ - Owner: ownerAddr, -}) -if err != nil { /* handle error */ } -utils.PrettyPrint(balResp) -``` - -The `suix_getAllBalances` RPC returns a list of coin types and balances that the address owns (e.g. SUI balance, and any other token types) ([GitHub - block-vision/sui-go-sdk: Go language SDK for @MystenLabs Sui](https://github.com/block-vision/sui-go-sdk#:~:text=,balance)) ([GitHub - block-vision/sui-go-sdk: Go language SDK for @MystenLabs Sui](https://github.com/block-vision/sui-go-sdk#:~:text=var%20cli%20%3D%20sui)). The SDK wraps this in `SuiXGetAllBalance` for convenience. Under the hood, Sui doesn’t store a simple number for an account’s balance – it sums up all coin object values for that owner. This call abstracts that logic for you. - -Other useful queries include `SuiXGetOwnedObjects` (list all objects owned by an address), `SuiGetTransactionBlock` (fetch details of a past transaction by its digest), and subscription endpoints for events. The BlockVision SDK covers most of these (and even provides a generic `SuiCall` method to call any RPC by name) ([GitHub - block-vision/sui-go-sdk: Go language SDK for @MystenLabs Sui](https://github.com/block-vision/sui-go-sdk#:~:text=utils)) ([GitHub - block-vision/sui-go-sdk: Go language SDK for @MystenLabs Sui](https://github.com/block-vision/sui-go-sdk#:~:text=)). In production, you might run your own Sui full node for queries or use a service provider’s RPC for reliability. - -### 4.4 Calling Move Contract Functions - -Interacting with Sui smart contracts (Move modules) is done by calling _entry functions_ via transactions. In Go SDK, this is facilitated by a `MoveCall` builder. Suppose we have published a Move package that includes a module `auction` with a public entry function `start_an_auction(item: ObjectID, initial_bid: u64, ...)`. To call it: - -```go -pkgID := "0x7d584c9a27ca4a546e8203b005b0e9ae746c9bec6c8c3c0bc84611bcf4ceab5f" // package containing the module -itemID := "0x342e959f8d9d1fa9327a05fd54fefd929bbedad47190bdbb58743d8ba3bd3420" // some object to auction -// ... other args like initial bid, etc. -gasObj := "0x58c103930dc52c0ab86319d99218e301596fda6fd80c4efafd7f4c9df1d0b6d0" - -tx, err := cli.MoveCall(ctx, models.MoveCallRequest{ - Signer: signerAccount.Address, - PackageObjectId: pkgID, - Module: "auction", - Function: "start_an_auction", - TypeArguments: []any{}, // any generic type args if needed - Arguments: []any{ itemID, /* other function args here */, gasObj /* etc */ }, - Gas: &gasObj, - GasBudget: "100000000", -}) -// (handle err) - -result, err := cli.SignAndExecuteTransactionBlock(ctx, models.SignAndExecuteTransactionBlockRequest{ - TxnMetaData: tx, PriKey: signerAccount.PriKey, - Options: models.SuiTransactionBlockOptions{ ShowEffects: true }, - RequestType: "WaitForLocalExecution", -}) -// (handle err and use result) -``` - -This is similar to the transfer example – we use `MoveCall` to construct the transaction ([GitHub - block-vision/sui-go-sdk: Go language SDK for @MystenLabs Sui](https://github.com/block-vision/sui-go-sdk#:~:text=rsp%2C%20err%20%3A%3D%20cli.MoveCall%28ctx%2C%20models.MoveCallRequest,Arguments%3A%20%5B%5Dinterface)) ([GitHub - block-vision/sui-go-sdk: Go language SDK for @MystenLabs Sui](https://github.com/block-vision/sui-go-sdk#:~:text=)), specifying the package ID, module name, function name, and the arguments. Notice even the `itemID` (an object we want to use) is passed in the `Arguments` list; Sui will take that object as an input to the Move function. The SDK expects `Arguments` as `[]any` which can include object IDs (as strings) or pure values (int, bool) matching the Move function signature. Once built, we sign and execute the transaction block the same way. If the function returns or emits events, those would appear in the execution effects. The **Move call transaction** is essentially a _programmable transaction_ invoking your custom logic on Sui. - -These examples show that the Go SDK abstracts much of the raw JSON-RPC complexity. Whether you’re transferring native coins, querying data, or invoking custom Move contracts, the library provides typed methods and handles the signing and encoding (including BCS serialization of Move arguments) for you. This accelerates integration development, allowing you to focus on application logic rather than low-level RPC formatting. - -## 5. Sui vs Ethereum: Key Differences and Migration Considerations - -Integrating Sui requires a shift in mindset for developers used to Ethereum. Below are the major differences in architecture and execution model, and tips for adapting to Sui’s design: - -- **Data Model – Objects vs Accounts:** Ethereum uses an account-based model where each account (EOA or contract) has a balance and storage (a key-value store). Sui uses an _object-based model_ ([Object Model | Sui Documentation](https://docs.sui.io/concepts/object-model#:~:text=The%20basic%20unit%20of%20storage,objects%20on%20the%20Sui%20network)) – assets are separate objects with their own storage, and an address merely _owns_ objects. There is no concept of an account storage mapping; instead, state is partitioned by object. For developers, this means you often query by object ID or by owner to get state, rather than reading a contract’s variables. You must explicitly pass objects to transactions, which makes data access patterns more predictable (and enables Sui’s parallelism). If coming from Ethereum, imagine each ERC-721 or ERC-20 token instance being a separate object rather than a slot in a contract’s storage – that’s how Sui operates at a fundamental level. - -- **Smart Contracts – Move vs Solidity:** Sui smart contracts are written in **Move**, not Solidity. Move’s resource-oriented approach enforces that certain data (resources) can only be created, transferred, or mutated in safe ways (cannot be accidentally duplicated, for instance). Ethereum devs will find similarities to Rust in Move’s syntax and to Solidity in the concept of modules (like contracts) and functions. But a key adaptation is learning to work without mechanisms like **reentrancy** (Move disallows calling back into a contract in the middle of execution) and without globally shared storage. Each Move call must specify its input objects and cannot touch others. For Solidity developers, this means re-thinking patterns like using a single contract to manage all user balances – instead, you might have a Move struct for a Coin resource and each user’s balance is one or more Coin objects they own. The upside is greater safety (no reentrancy bugs, no accidental token supply inflation) and automatic parallelism. The downside is a learning curve: Move is a newer language with a smaller ecosystem of examples and libraries than Solidity. However, tools like the Move Prover (for formal verification) and a growing community are making Move development more approachable. _Tip:_ Read Sui’s Move-specific documentation to understand differences from Ethereum’s model (for example, Sui Move’s lack of `self` contract storage and the requirement to use object references) ([Move Concepts | Sui Documentation](https://docs.sui.io/concepts/sui-move-concepts#:~:text=,take%20object%20references%20as%20input)) ([Move Concepts | Sui Documentation](https://docs.sui.io/concepts/sui-move-concepts#:~:text=In%20Move%20on%20Diem%2C%20global,move_from)). - -- **Execution Model:** Ethereum processes transactions **sequentially** in a total order (each block is an ordered list of txns determined by gas bidding and miner/validator scheduling). This means even two independent transfers must wait their turn in the chain. Sui, by contrast, achieves **parallel execution** of independent transactions by using the object-centric approach ([Sui Blockchain: A Deep Dive](https://stakin.com/blog/sui-blockchain-a-deep-dive#:~:text=In%20addition%2C%20by%20leveraging%20the,significantly%20enhancing%20throughput%20and%20efficiency)) ([Sui Blockchain: A Deep Dive](https://stakin.com/blog/sui-blockchain-a-deep-dive#:~:text=Unlike%20these%20traditional%20models%2C%20Sui%27s,friendly%20platform)). Transactions touching different objects (or only objects owned by the sender) can be executed concurrently without conflicts. Ethereum is introducing parallelism at the VM level (e.g. via danksharding or Proto-danksharding for data, and some EVM parallel execution research), but it’s fundamentally constrained by needing a single, globally consistent state per block. On Sui, the _transaction is the unit of scheduling_, not the block. As described earlier, Sui bypasses consensus for non-conflicting transactions ([Sui Blockchain: A Deep Dive](https://stakin.com/blog/sui-blockchain-a-deep-dive#:~:text=Transactions%20that%20don%27t%20involve%20mutably,the%20capacity%20for%20parallel%20processing)), allowing much higher throughput and lower latency. For a dev, this means under high load your Sui transactions might still confirm quickly if they don’t hit the same shared contract as others – whereas on Ethereum, high global demand raises gas prices for everyone. Also, because Sui has no global nonce per account, you don’t worry about transaction ordering from one user beyond object locks (the user could even have multiple txns in flight as long as they use distinct objects). - -- **Consensus and Finality:** Ethereum (post-Merge) uses a Proof-of-Stake consensus (Casper FFG on the Beacon Chain) to produce and finalize blocks. Blocks reach _probabilistic finality_ in seconds and _deterministic finality_ after 2-3 epochs (~12 minutes) when justified and finalized checkpoints are achieved. Sui’s consensus differs: it achieves **inherent finality** per transaction certificate. An executed certified transaction is final immediately (given the >2/3 honest assumption) ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=After%20a%20validator%20executes%20a,that%20transaction%20to%20the%20network)) ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=1,void%20after%20an%20epoch%20change)), and later sealed in a checkpoint for archiving. Sui’s consensus (covered in detail in the next section) is optimized for the object model: it separates the ordering of unrelated transactions. For an Ethereum developer, it’s important to realize there is no concept of “pending mempool with gas bidding” – either your tx is certified and will execute, or it’s not. Gas price is usually fixed (all users pay roughly the same), so the developer’s focus shifts from gas price strategy to ensuring correct object usage and sufficient gas budget. - -- **Tooling and Developer Ecosystem:** Ethereum enjoys a mature ecosystem – tools like Hardhat, Truffle, MetaMask, OpenZeppelin libraries, Etherscan, The Graph, etc. Sui’s ecosystem is newer but growing. Instead of MetaMask, Sui users have wallets like Sui Wallet or Ethos Wallet (which support Sui’s key schemes and object displays). Instead of Hardhat, developers use the Sui CLI and Move build tools to compile and publish Move packages. For debugging, Ethereum has a rich suite of Solidity debuggers and testnets; Sui provides a local emulator (`sui test`, Move unit tests, and a test validator for local networks). Sui Explorer serves a similar role to Etherscan, allowing you to lookup object IDs, transaction certificates, etc., but it may feel less familiar than Ethereum’s account-based views. **Developers transitioning from Ethereum** should allocate time to set up these new tools and read Sui’s documentation, especially around **programmable transaction blocks (PTBs)** which allow batching multiple calls in one transaction – a concept somewhat akin to Ethereum’s multicall, but natively supported in Sui’s transaction format. - -- **Design Patterns:** Some Ethereum design patterns don’t translate directly. For example, “approve and transferFrom” patterns (common in ERC-20) aren’t needed in Sui – you simply transfer the object (coin) itself; the owner’s signature is inherently required. Reentrancy guards aren’t needed due to Move’s call discipline. Instead, new patterns emerge, like object **wrapping** (encapsulating an object inside another for atomic actions) or using **shared objects** sparingly to avoid consensus bottlenecks. Developers coming from Ethereum should _unlearn global state assumptions_ – e.g., a Sui contract can’t iterate over all users of a dApp unless it has stored references to their objects. You often need to maintain lists of object IDs or utilize event indexing off-chain to achieve similar functionality. - -In short, Sui’s architecture is **more decentralized at the data level** – the chain doesn’t maintain one big world state dictionary, but a graph of objects and transactions ([Object Model | Sui Documentation](https://docs.sui.io/concepts/object-model#:~:text=The%20set%20of%20objects%20that,immutable%20objects%20in%20the%20system)) ([Object Model | Sui Documentation](https://docs.sui.io/concepts/object-model#:~:text=history,owned%20by%20a%20single%20address)). This yields performance benefits and safer smart contracts (many common Ethereum bugs are eliminated by construction), but it requires a new way of thinking about state and access control. Ethereum developers should embrace Move’s stricter model as a feature, not a limitation: by explicitly passing objects and specifying authorities, you get determinism and parallelism that Ethereum can’t match. With Sui’s design, if you avoid unnecessary shared objects, your dApp can achieve massive scalability that would be very complex on Ethereum. - -## 6. Developer Environment Setup (Sui CLI, Local Network, and SDKs) - -Getting started with Sui development involves setting up the Sui node software (for local testing and CLI tools) and any SDKs you need. Below are steps to prepare a Sui development environment: - -- **Install Rust (if building from source):** Sui is written in Rust. Ensure you have the latest stable Rust and Cargo installed ([Install Sui | Sui Documentation](https://docs.sui.io/guides/developer/getting-started/sui-install#:~:text=%24%20cargo%20install%20,features%20tracing)) ([Install Sui | Sui Documentation](https://docs.sui.io/guides/developer/getting-started/sui-install#:~:text=To%20update%20to%20the%20latest,stable%20version%20of%20Rust)) if you plan to compile Sui yourself. Rust 1.70+ is recommended. On Linux, also install build tools (clang, etc.) as per Sui’s prerequisites. - -- **Install Sui Binaries:** There are multiple options: - - - **Via Precompiled Binaries:** Download the Sui release for your OS from the official GitHub and add the binaries (`sui`, `sui-node`, etc.) to your PATH. This is quick if you find a release bundle. (The Sui docs offer an install wizard for this.) - - **Via Cargo (source build):** You can compile Sui from source using Cargo. For example: - ```bash - cargo install --locked --git https://github.com/MystenLabs/sui.git --branch sui --features "tracing" - ``` - Replace `` with `devnet`, `testnet`, or `mainnet` to target a specific network branch ([Set Up Environment - Sui Move Intro Course](https://intro.sui-book.com/unit-one/lessons/1_set_up_environment.html#:~:text=2)). For latest stable, you might use the `testnet` or `mainnet` branch. This will compile the Sui CLI and related binaries and install them to Cargo’s bin directory. (The `tracing` feature is recommended to enable Move debugging support ([Install Sui | Sui Documentation](https://docs.sui.io/guides/developer/getting-started/sui-install#:~:text=%24%20cargo%20install%20,features%20tracing)) ([Install Sui | Sui Documentation](https://docs.sui.io/guides/developer/getting-started/sui-install#:~:text=To%20update%20to%20the%20latest,stable%20version%20of%20Rust)).) - - **Via Docker:** If you prefer not to install Rust, Mysten provides a Docker image `mysten/sui-tools` with the Sui binaries pre-installed. You can run `docker pull mysten/sui-tools:devnet` (or `:testnet`) then start a container ([Set Up Environment - Sui Move Intro Course](https://intro.sui-book.com/unit-one/lessons/1_set_up_environment.html#:~:text=Using%20a%20Docker%20Image%20with,installed%20Sui%20Binaries)) ([Set Up Environment - Sui Move Intro Course](https://intro.sui-book.com/unit-one/lessons/1_set_up_environment.html#:~:text=2,image)). This is convenient for an isolated environment or on Windows. You can exec into the container to use `sui` CLI inside. - - After installation, verify by running `sui --version` ([Install Sui | Sui Documentation](https://docs.sui.io/guides/developer/getting-started/sui-install#:~:text=)). You should see a version printout instead of “command not found”. - -- **Set Up Sui Client and Network:** The Sui CLI tool (`sui`) allows you to interact with the network and manage keys. On first run, it might prompt whether to connect to a Sui full node – you can choose “Yes” and pick Devnet or Testnet to quickly get started ([Set Up Environment - Sui Move Intro Course](https://intro.sui-book.com/unit-one/lessons/1_set_up_environment.html#:~:text=Initialization)). Alternatively, use `sui client switch --env devnet` or `sui client new-env --alias localnet --rpc http://127.0.0.1:9000` to configure environments ([Set Up Environment - Sui Move Intro Course](https://intro.sui-book.com/unit-one/lessons/1_set_up_environment.html#:~:text=Managing%20Networks)). The CLI maintains a config folder (`~/.sui/sui_config`) with network endpoints and a keystore. - -- **Key Management:** Sui supports Ed25519 (default) and Secp256k1 keys for accounts. You can generate a new keypair with `sui client new-address ed25519` or import an existing mnemonic with `sui client import`. The CLI will store keys in an encrypted file. Use `sui client active-address` to see the default address and `sui client gas` to list your SUI coin objects (initially none until you get some tokens) ([Set Up Environment - Sui Move Intro Course](https://intro.sui-book.com/unit-one/lessons/1_set_up_environment.html#:~:text=Check%20Active%20Address%20and%20Gas,Objects)) ([Set Up Environment - Sui Move Intro Course](https://intro.sui-book.com/unit-one/lessons/1_set_up_environment.html#:~:text=,sui%20client%20gas)). - -- **Running a Local Network:** For local testing of your Move contracts or integration, you can run a local Sui validator. The binary `sui-test-validator` can start a single-node network on your machine ([Install Sui | Sui Documentation](https://docs.sui.io/guides/developer/getting-started/sui-install#:~:text=%60sui,in)) ([Install Sui | Sui Documentation](https://docs.sui.io/guides/developer/getting-started/sui-install#:~:text=%60sui,button)). Simply run `sui test-validator` (after building Sui) in a separate terminal; it will create a temporary genesis and start listening on 127.0.0.1:9000 for RPC. The CLI will automatically use `localnet` env for this. This is analogous to Ganache/Hardhat EVM local node for Ethereum. It allows fast iterative testing without needing faucet tokens. - -- **Faucet for Test Tokens:** On Devnet or Testnet, you’ll need SUI tokens to pay gas. Sui provides a faucet service. If you have the Sui CLI, you can run `sui client request-sui [--env testnet]` to request coins from the faucet (this hits a faucet URL internally). Or use the Discord faucet: join Sui’s Discord and post `!faucet ` in the `#devnet-faucet` or `#testnet-faucet` channel ([Set Up Environment - Sui Move Intro Course](https://intro.sui-book.com/unit-one/lessons/1_set_up_environment.html#:~:text=Get%20Devnet%20or%20Testnet%20Sui,Tokens)). The faucet will send a small amount of SUI to your address for testing. In our Go examples above, we showed an SDK method `RequestSuiFromFaucet` as well ([GitHub - block-vision/sui-go-sdk: Go language SDK for @MystenLabs Sui](https://github.com/block-vision/sui-go-sdk#:~:text=func%20RequestDevNetSuiFromFaucet%28%29%20,err%29%20return)) ([GitHub - block-vision/sui-go-sdk: Go language SDK for @MystenLabs Sui](https://github.com/block-vision/sui-go-sdk#:~:text=recipient%20%3A%3D%20)). - -- **Move Development Environment:** If you plan to write Move contracts, install the **Move compiler and tools**. The Sui repository includes Move build tools invoked via `sui move build` and `sui move publish`. No separate download is needed – the Sui CLI doubles as a Move CLI (`sui move` subcommands). Make sure to get a code editor plugin for Move (for VS Code, the Move Analyzer plugin is recommended ([Set Up Environment - Sui Move Intro Course](https://intro.sui-book.com/unit-one/lessons/1_set_up_environment.html#:~:text=,in))). The plugin may require the Move language server – installed via `cargo install --git https://github.com/move-language/move move-analyzer --features "address20"` (the `address20` feature adds support for Sui’s 20-byte addresses in Move) ([Set Up Environment - Sui Move Intro Course](https://intro.sui-book.com/unit-one/lessons/1_set_up_environment.html#:~:text=2,style%20wallet%20addresses)). - -- **Go SDK installation:** If your application is in Go, you’ve already installed the Go SDK above (`go get ...`). For other languages, community SDKs are available: TypeScript (official), Rust (official), Python (`pysui`), Java/Kotlin, Swift, Dart, etc. ([Sui and Community SDKs | Sui Documentation](https://docs.sui.io/references/sui-sdks#:~:text=Community%20SDKs)) ([Sui and Community SDKs | Sui Documentation](https://docs.sui.io/references/sui-sdks#:~:text=A%20cross,mobile%2C%20web%2C%20and%20desktop)). Ensure you include the right dependency in your project. For example, in Go, the BlockVision SDK uses Go modules (`import "github.com/block-vision/sui-go-sdk/sui"` etc.). - -- **Testing the setup:** As a quick test, use the CLI to ping the network. Try `sui client state` to show your addresses and object list or `sui client objects
` to list object summaries. If running localnet, publish a sample Move package: the Sui docs’ examples (e.g. a basic counter contract) can be built with `sui move build` and published with `sui client publish --gas-budget 30000`. This will create a new package object on your local network. You can then try calling its functions with `sui client call --function ...` or, in Go, via the SDK’s `MoveCall`. - -This environment setup ensures you have: Sui node/CLI for low-level control and Move contract deployment, and the Go SDK (or any SDK of choice) for building your application that interacts with Sui. Always keep your Sui CLI and node updated to match the network version (especially around testnet resets or mainnet upgrades). And for Move development, the CLI’s built-in unit testing (`sui move test`) is invaluable for quick iteration. - -## 7. Sui’s Validator & Consensus Mechanism (vs Ethereum) - -Sui runs a delegated Proof-of-Stake (PoS) network with a set of validators (similar to Ethereum’s validator set) that change every **epoch**. Sui’s approach to consensus, however, is quite novel and differs from Ethereum’s chain-based consensus in several ways: - -- **Byzantine Consistent Broadcast (Fast Path):** Sui is designed so that many transactions can avoid full consensus. As mentioned, transactions affecting only owned objects do not need global ordering. Sui employs a mechanism akin to **Byzantine Consistent Broadcast (BCB)** (inspired by the FastPay protocol) for these cases ([What is Sui Blockchain - Sui Ecosystem Explained](https://everstake.one/blog/what-is-sui-blockchain-sui-ecosystem-explained#:~:text=Sui%E2%80%99s%20consensus%20mechanism%20is%20divided,or%20Tusk%20for%20shared%20objects)) ([What is Sui Blockchain - Sui Ecosystem Explained](https://everstake.one/blog/what-is-sui-blockchain-sui-ecosystem-explained#:~:text=execution%20layer%2C%20while%20its%20Byzantine,minimalized%20peer%20review%20among%20validators)). In essence, the client collects signatures from a supermajority of validators (forming a certificate) – this achieves a _consensus-less agreement_ that the transaction is valid. It’s consensus-less in that validators didn’t all talk to each other or run a consensus algorithm to order this one transaction; the client assembled the proof. This fast path is highly efficient and can confirm transactions with just a round of signatures, giving Sui its low latency edge. - -- **Narwhal & Bullshark (Consensus Path):** For ordering transactions that do involve **shared objects** (or simply to provide a total order of events for checkpointing), Sui uses a two-part consensus engine: **Narwhal** and **Bullshark**. Narwhal is a DAG-based mempool that ensures data availability – validators gossip transactions in a DAG, which can grow even without agreeing on ordering ([What is Sui Blockchain - Sui Ecosystem Explained](https://everstake.one/blog/what-is-sui-blockchain-sui-ecosystem-explained#:~:text=while%20shared%20objects%20require%20consensus,or%20Tusk%20for%20shared%20objects)). Bullshark (an evolution of the Tusk consensus protocol) is a Byzantine Fault Tolerant algorithm that operates on Narwhal’s DAG to establish an ordering (i.e., pick which DAG nodes become final in each round) ([What is Sui Blockchain - Sui Ecosystem Explained](https://everstake.one/blog/what-is-sui-blockchain-sui-ecosystem-explained#:~:text=Sui%E2%80%99s%20consensus%20mechanism%20is%20divided,or%20Tusk%20for%20shared%20objects)). Together, Narwhal+Bullshark provide high-throughput and low-latency consensus even under adverse conditions. In plain terms, Narwhal handles the _networking & data propagation_; Bullshark performs the _leaderless consensus_ on that data. This design is resilient and has been shown to achieve finality in under half a second under certain conditions, via a protocol nicknamed **Mysticeti** (an update that further cut latency) ([Sui Sets The Standard for Blockchain Speed with New Mainnet ...](https://bravenewcoin.com/insights/sui-sets-the-standard-for-blockchain-speed-with-new-mainnet-consensus-mechanism#:~:text=Sui%20Sets%20The%20Standard%20for,developer%20stack%20to%20new%20highs)). For developers, the important point is that if your transaction needs consensus (e.g. interacting with a shared object like a widely-used contract), it will still finalize quickly and fairly (no miner extractable value auctions like Ethereum – all validators run Bullshark uniformly). - -- **Checkpointing and Finality:** Ethereum finality involves the whole chain – a block is final when 2/3 of validators have attested to a checkpoint that includes that block. In Sui, _finality is more granular_. A certified transaction is effectively final (as long as the current epoch validators remain). Sui then groups transactions into **checkpoints** periodically (e.g. each ~ sui consensus round) which serve as global points of finality – these are used for things like rewarding validators and enabling light clients to sync. If an epoch ends (validators change), any not-yet-executed certificates must be re-certified in the new epoch (to prevent carry-over issues) ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=1,void%20after%20an%20epoch%20change)) ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=3,it%20takes%20a)). But under normal operation, an executed transaction is final within its epoch. This difference means Sui doesn’t suffer from chain re-orgs in the traditional sense; forks are avoided by design. Ethereum’s safety comes from slashing if a conflicting finality is derived, whereas Sui’s comes from quorum intersection and straight-line checkpoints. - -- **Validator Duties and Stake:** Similar to Ethereum, Sui validators stake SUI (often delegated from token holders) and earn rewards from gas fees. There is a staking epoch system (e.g., epochs last 24 hours on Sui Mainnet, after which stake rewards are distributed and the reference gas price may adjust). Sui validators also have to run the additional components (Narwhal mempool). Ethereum validators must run both a consensus client and an execution client; in Sui, those roles are fused – a Sui validator runs an **authority process** that both validates transactions and participates in consensus as needed. Ethereum’s proposer-builder separation and slot leader election have no direct analogue in Sui; all validators are more symmetrically involved in certification and consensus. - -**Consensus Comparison:** In Ethereum, every transaction is agreed upon via the global block consensus (even two independent token transfers must be ordered in one chain). In Sui, consensus is _conditional_ – it kicks in only when required for consistency. This is why Sui’s documentation emphasizes two modes: “owned objects skip consensus and only go through validation, while shared objects require consensus for ordering” ([What is Sui Blockchain - Sui Ecosystem Explained](https://everstake.one/blog/what-is-sui-blockchain-sui-ecosystem-explained#:~:text=Sui%E2%80%99s%20consensus%20mechanism%20is%20divided,or%20Tusk%20for%20shared%20objects)). In practice, Sui’s consensus can be viewed as a multi-lane highway (parallel lanes for independent txns, merging into a single lane when a global merge is needed), whereas Ethereum is a single lane where all cars queue. As a developer, you don’t explicitly choose the path – Sui’s protocol does it under the hood based on your transaction content. But it’s useful to know: if you design your contracts to use mostly owned objects (per user) and minimize shared/global state, your dApp will enjoy higher throughput on Sui. - -In terms of **security**, both Ethereum and Sui are BFT PoS systems requiring >2/3 honest validators. Ethereum slashes misbehavior like equivocation; Sui can also slash by cutting future stake rewards. Sui’s Narwhal+BFT ensures liveness and fairness similar to Ethereum’s Gossip + Casper. One could say Ethereum prioritizes a _totally ordered log of transactions_ (great for general composability but with bottlenecks), while Sui prioritizes _throughput by partial ordering_ (great for parallelism, with the caveat that cross-shard or cross-shared-object interactions might be less straightforward). For most integration purposes, you can trust that Sui’s consensus will give you finality on par with Ethereum’s (and faster), but if you run a Sui full node, be mindful of epoch transitions and reconfigurations which slightly differ from Ethereum’s continuous chain. - -## 8. Common Integration Challenges and Best Practices - -Building on a new blockchain like Sui can introduce challenges that developers need to plan for. Below are some real-world integration challenges and how to address them: - -- **Learning Curve & Move Adoption:** Sui’s use of Move is a double-edged sword – it offers safety and performance, but it’s new to most developers. In fact, broader _developer adoption of Sui Move_ is noted as a key challenge for the ecosystem ([Demystifying the SUI Blockchain: A Comprehensive Overview](https://www.ulam.io/blog/demystifying-the-sui-blockchain-a-comprehensive-overview#:~:text=While%20SUI%20has%20made%20significant,associated%20with%20its%20unique%20language)). Teams may need to train developers or hire Move experts. **Best practice:** Start with Sui’s official Move tutorials and the Sui Move Book. Leverage the similarity to Rust for developers with that background. Write extensive Move unit tests to build confidence, and use the Move Prover for critical contracts. Over time, the community is growing and more libraries (like a Sui equivalent of OpenZeppelin’s contracts) will emerge. - -- **Community SDK Maturity:** With no official Go or Java SDK from Mysten, developers rely on community libraries. These may not always be perfectly up-to-date with the latest node version or could have bugs. For example, if Sui introduces new RPC fields or changes event formats, the Go SDK might need updates. **Best practice:** Pin your dependency to a stable release of the SDK that matches the Sui version you target. Write integration tests for your usage of the SDK (perhaps using the local test-validator) – this can catch any incompatibility early. Keep an eye on the SDK’s GitHub for updates or issues reported by others. In a pinch, you can always fall back to direct JSON-RPC calls (the Go SDK’s `SuiCall` method or using the `net/http` client) for any functionality not yet wrapped nicely. - -- **Object Versioning and State Consistency:** As mentioned under gas, one common hiccup is **stale object references**. If your application caches an object ID and version (e.g. a coin or an NFT) and tries to use it in a transaction after it’s been changed by a previous transaction, Sui will reject it (object version mismatch). In Ethereum, you might equivalently forget to update a nonce. In Sui, _every object is like its own nonce_. **Best practice:** Always fetch the latest object reference (ID, version, digest) for objects you plan to use as inputs. The Sui RPC has batch endpoints (`sui_multiGetObjects`) – use them to refresh your view of critical objects (like a user’s primary coin for gas) before building a transaction. If you do get an error about object version or lock conflict, handle it gracefully: refresh the state and retry. This is especially important in high-concurrency scenarios. Also, use `WaitForLocalExecution` or check effects to ensure a transaction succeeded before assuming an object’s new state. - -- **Gas Coin Management:** Users can accumulate many small SUI coin objects over time (like “change” from transactions). If your app always picks the first coin for gas, that coin could become depleted. Conversely, if the coin has much more than needed, the user might prefer not to spend from it. **Best practice:** Implement coin management strategy – e.g., pick the smallest coin that covers the budget (to minimize fragmentation), or automatically merge coins (Sui SDK provides a `MergeCoins` transaction type) when the user’s balance is split into too many pieces. The BlockVision SDK has convenience for merging coins ([GitHub - block-vision/sui-go-sdk: Go language SDK for @MystenLabs Sui](https://github.com/block-vision/sui-go-sdk#:~:text=)) ([GitHub - block-vision/sui-go-sdk: Go language SDK for @MystenLabs Sui](https://github.com/block-vision/sui-go-sdk#:~:text=func%20main%28%29%20,BvTestnetEndpoint)). This will improve user experience by keeping their asset management simple. - -- **Handling Epoch Changes:** Sui operates in epochs. If your integration is long-running (server-side processes, etc.), be aware that at epoch boundaries some operations might need to refresh. Notably, a transaction certificate from a previous epoch is no longer valid once a new epoch starts ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=not%20guarantee%20finality,node%20implementation%20handles%20this%20automatically)) ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=match%20at%20L395%201,void%20after%20an%20epoch%20change)). Usually the Sui full node will handle this (by not letting you form outdated certs), but if you hold onto a partially executed transaction, it won’t carry over. **Best practice:** Design your workflows assuming transactions should complete within the epoch they started. If an epoch change is imminent (you can query the epoch via RPC), perhaps hold off on sending non-urgent transactions for a moment. Ensure your system can tolerate a brief pause during epoch rollover (which on Mainnet occurs daily). On the plus side, Sui epochs are deterministic and planned, not surprise forks. - -- **Event Indexing and Queries:** In Ethereum, a lot of integration logic relies on reading contract events via filters (e.g., logs for transfers). Sui also has events (Move modules can emit events which are stored), and the RPC allows querying events by module, sender, etc. However, as of now, Sui’s event querying might not be as robust or scalable for analytics as Ethereum’s (with TheGraph, etc.). If your integration needs to watch certain on-chain events (like an order book filling, or an NFT being minted), you might need to run a custom indexer or poll the RPC frequently. Mysten provides a Sui Indexer service and there are community indexers. **Best practice:** Use the **subscription API** (websocket) for real-time needs – the Go SDK supports subscribing to events or transactions so you can get push notifications instead of polling ([GitHub - block-vision/sui-go-sdk: Go language SDK for @MystenLabs Sui](https://github.com/block-vision/sui-go-sdk#:~:text=,events%20or%20transactions%20via%20websockets)). For complex queries, consider using the GraphQL API in Sui or running a database that ingests Sui data. - -- **Security and Auditing:** As with any blockchain, ensure you follow best practices: audit your Move code (independent audits, use the Move Prover for invariants), and be mindful of typical bugs. Sui eliminates some Ethereum issues, but new ones can appear (e.g., forgetting to properly restrict a public fun in Move could let anyone call it). On the integration side, protect your keys – for instance, **never hardcode private keys or mnemonics** in your source (a reminder from Sui’s security team ([Security Best Practices for Building on Sui](https://blog.sui.io/security-best-practices/#:~:text=Use%20different%20addresses%20on%20Devnet%2C,Testnet%2C%20and%20Mainnet)) ([Security Best Practices for Building on Sui](https://blog.sui.io/security-best-practices/#:~:text=Hardcoded%20keys%20and%20mnemonics%20shouldn%E2%80%99t,stored%20in%20public%20GitHub%20repos))). Use environment variables or secure vaults for any service keys. - -- **Network Maturity:** As a newer chain, Sui’s network may experience more frequent upgrades and changes than Ethereum. For example, during testnet phases, breaking changes and resets occurred. Even on mainnet, keep an eye on releases (the Sui Foundation typically announces upgrades well in advance). Ensure your node or RPC provider is up-to-date; outdated software can lead to integration errors. **Best practice:** Follow Sui’s official channels for updates, pin dependencies to specific versions, and if possible include version checks in your app (the RPC has an API to get the current protocol version). This way, you can gracefully handle or alert when an upgrade might affect your app. - -In conclusion, integrating Sui offers high performance and unique capabilities (like batching multiple actions in one transaction block) but requires adjusting to its novel concepts. By understanding Sui’s object model, transaction flow, and differences from Ethereum, developers can avoid common pitfalls. As Sui continues to evolve, its tooling and documentation are rapidly improving – so staying engaged with the community (forums, Discord, GitHub) will help you keep best practices up-to-date. With careful design and the strategies outlined above, you can leverage Sui’s speed and scalability in production applications while minimizing integration headaches. - -## **References** (Documentation and Sources) - -- Sui Official Documentation – **Object Model & Move** ([Object Model | Sui Documentation](https://docs.sui.io/concepts/object-model#:~:text=The%20basic%20unit%20of%20storage,objects%20on%20the%20Sui%20network)) ([Move Concepts | Sui Documentation](https://docs.sui.io/concepts/sui-move-concepts#:~:text=Move%20is%20an%20open%20source,optimization%20on%20the%20Sui%20blockchain)) -- Sui Official Documentation – **Transaction Lifecycle** ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=1,they%20own%20and%20shared%20objects)) ([Life of a Transaction | Sui Documentation](https://docs.sui.io/concepts/sui-architecture/transaction-lifecycle#:~:text=Then%2C%20each%20validator%20does%20the,based%20on%20whether%20the%20transaction)) -- Sui Official Documentation – **Gas and Fees** ([Gas in Sui | Sui Documentation](https://docs.sui.io/concepts/tokenomics/gas-in-sui#:~:text=A%20Sui%20transaction%20must%20pay,pays%20the%20following%20gas%20fees)) ([Gas in Sui | Sui Documentation](https://docs.sui.io/concepts/tokenomics/gas-in-sui#:~:text=The%20gas%20budget%20for%20a,transactions%20are%20successfully%20executed%20if)) -- BlockVision Sui-Go-SDK – **GitHub README and Examples** ([GitHub - block-vision/sui-go-sdk: Go language SDK for @MystenLabs Sui](https://github.com/block-vision/sui-go-sdk#:~:text=gasObj%20%3A%3D%20)) ([GitHub - block-vision/sui-go-sdk: Go language SDK for @MystenLabs Sui](https://github.com/block-vision/sui-go-sdk#:~:text=rsp%2C%20err%20%3A%3D%20cli.SuiGetObject%28ctx%2C%20models.SuiGetObjectRequest,true)) -- Stakin Blog – _“Sui Blockchain Deep Dive”_ (Object-centric model and parallel execution) ([Sui Blockchain: A Deep Dive](https://stakin.com/blog/sui-blockchain-a-deep-dive#:~:text=Transactions%20that%20don%27t%20involve%20mutably,the%20capacity%20for%20parallel%20processing)) ([Sui Blockchain: A Deep Dive](https://stakin.com/blog/sui-blockchain-a-deep-dive#:~:text=Unlike%20these%20traditional%20models%2C%20Sui%27s,friendly%20platform)) -- Everstake Blog – _“What is Sui Blockchain”_ (Consensus mechanism summary) ([What is Sui Blockchain - Sui Ecosystem Explained](https://everstake.one/blog/what-is-sui-blockchain-sui-ecosystem-explained#:~:text=Sui%E2%80%99s%20consensus%20mechanism%20is%20divided,or%20Tusk%20for%20shared%20objects)) -- Ulam Labs – _“Demystifying Sui”_ (Challenges and Move adoption) - -## Original Prompt - -I am integrating Sui into an application and need a clear technical report on its blockchain fundamentals and development workflow. The report should cover: - -1. Core blockchain data structures, including how the Move programming language is used in Sui. -2. The transaction lifecycle from submission to finality. -3. Gas mechanics—how gas fees are calculated, paid, and impact transactions. -4. Golang support: - - Existing SDKs or libraries. - - Code examples for interacting with Sui (e.g., sending transactions, querying state). -5. A detailed comparison with EVM-based blockchains, highlighting key differences in architecture, smart contract execution, and performance. - -Additionally, if relevant, include insights on: - -- Developer tools (CLI, SDKs, APIs). -- Validator and consensus mechanisms. -- Common challenges in integrating Sui. - -I am looking for a mix of technical depth and practical guidance, with code examples where applicable. - -## Suggestions to the model: - -Preferred format: article-style guide -Code Examples Depth: production-level examples. Make sure you use the most popular Sui Go community library since there are no official ones; -Comparison with EVM-Based Blockchains: focus only on Ethereum -Level of Practical Guidance: please provide hands-on steps for setting up a development environment diff --git a/knowledge_base/txm/aptos_txm.md b/knowledge_base/txm/aptos_txm.md deleted file mode 100644 index 2b87be29..00000000 --- a/knowledge_base/txm/aptos_txm.md +++ /dev/null @@ -1,198 +0,0 @@ -## **Aptos Transaction Manager: Overview and Architecture** - -The provided Go files implement a **Transaction Manager (TxM)** for Aptos, responsible for handling transaction lifecycle events, from creation to finalization. Below is a structured breakdown of its main components and their responsibilities. - ---- - -### **Main Components** -The transaction manager consists of several key components: - -1. **`AptosTxm` (Transaction Manager) - `txm.go`** -2. **`AptosTx` (Transaction Data Structure) - `tx.go`** -3. **`TxStore` (Transaction Store) - `txstore.go`** -4. **`AccountStore` (Account-based Transaction Tracking) - `txstore.go`** -5. **`Config` (Configuration Management) - `config.go`** -6. **`bcs_util.go` (BCS Serialization Utilities)** - ---- - -### **High-Level Architecture** -```mermaid -graph TD - A[User Request] -->|Calls Enqueue| B[AptosTxm] - B -->|Creates Tx Object| C[AptosTx] - B -->|Stores Tx| D[TxStore] - D -->|Manages Nonces| E[AccountStore] - B -->|Serializes & Signs Tx| F[BCS Utilities] - F -->|Submits Tx| G[Aptos Blockchain] - G -->|Returns Status| B - B -->|Handles Confirmation & Retry| D -``` - ---- - -## **1. `AptosTxm` (Transaction Manager)** -The **AptosTxm** struct is the central component managing transactions. - -### **Responsibilities:** -- Handles **transaction creation** via `Enqueue()` -- **Manages** transaction lifecycle (broadcast, confirmation) -- **Serializes & signs** transactions before submission -- Handles **failures & retries** if needed -- Maintains a **thread-safe transaction queue** -- **Prunes** old transactions periodically - -### **Key Methods:** -| Method | Description | -|--------|-------------| -| `New()` | Initializes the transaction manager. | -| `Enqueue()` | Adds a transaction to be processed. | -| `Start()` | Starts background loops for broadcasting & confirmation. | -| `Close()` | Gracefully shuts down transaction processing. | -| `GetStatus()` | Fetches the status of a given transaction. | -| `broadcastLoop()` | Continuously sends transactions to the Aptos blockchain. | -| `confirmLoop()` | Periodically checks if transactions are finalized. | -| `signAndBroadcast()` | Serializes, signs, and submits transactions to Aptos. | -| `maybeRetry()` | Handles retry logic if transactions fail. | - ---- - -## **2. `AptosTx` (Transaction Data Structure)** -This struct represents an **Aptos transaction**. - -### **Structure:** -```go -type AptosTx struct { - ID string - Metadata *commontypes.TxMeta - Timestamp uint64 - FromAddress aptos.AccountAddress - PublicKey ed25519.PublicKey - ContractAddress aptos.AccountAddress - ModuleName string - FunctionName string - TypeTags []aptos.TypeTag - BcsValues [][]byte - Attempt uint64 - Status commontypes.TransactionStatus - Simulate bool -} -``` - -### **Responsibilities:** -- Stores transaction details such as: - - Sender, contract, function, parameters - - Status tracking (`Pending`, `Confirmed`, `Failed`) - - Tracks attempts and retries -- Used by **TxStore** and **AptosTxm** to track progress. - ---- - -## **3. `TxStore` (Transaction Storage & Nonce Tracking)** -A **per-account transaction store** that: -- Tracks **unconfirmed transactions** -- Manages **nonces** to ensure correct sequencing -- Handles **failed transactions** to retry them properly - -### **Key Methods:** -| Method | Description | -|--------|-------------| -| `NewTxStore()` | Initializes a transaction store. | -| `GetNextNonce()` | Returns the next nonce to use. | -| `AddUnconfirmed()` | Marks a transaction as unconfirmed. | -| `Confirm()` | Marks a transaction as finalized or failed. | -| `GetUnconfirmed()` | Retrieves unconfirmed transactions. | -| `InflightCount()` | Returns the count of unconfirmed transactions. | - ---- - -## **4. `AccountStore` (Account-based Transaction Tracking)** -The **AccountStore** manages multiple `TxStore` instances, each tracking transactions for a specific account. - -### **Responsibilities:** -- Provides **isolation** between accounts. -- Stores multiple `TxStore` instances. -- Allows **batch retrieval** of unconfirmed transactions. -- Maintains **in-flight transaction count**. - -### **Key Methods:** -| Method | Description | -|--------|-------------| -| `NewAccountStore()` | Creates a new account store. | -| `CreateTxStore()` | Creates a TxStore for an account. | -| `GetTxStore()` | Retrieves the TxStore for an account. | -| `GetTotalInflightCount()` | Counts all in-flight transactions. | - ---- - -## **5. `Config` (Configuration Management)** -Defines various **timeouts, retry limits, and gas parameters**. - -### **Configuration Fields** -| Parameter | Description | Default | -|-----------|-------------|---------| -| `BroadcastChanSize` | Max transactions in queue | `100` | -| `ConfirmPollSecs` | Interval for checking confirmations | `2s` | -| `DefaultMaxGasAmount` | Gas limit per transaction | `200,000` | -| `MaxSimulateAttempts` | Retry attempts for simulations | `5` | -| `MaxSubmitRetryAttempts` | Retries for transaction submission | `10` | -| `SubmitDelayDuration` | Delay between retries | `3s` | -| `TxExpirationSecs` | Expiration time for a transaction | `10s` | -| `MaxTxRetryAttempts` | Max transaction retries | `5` | -| `PruneIntervalSecs` | Interval for pruning old transactions | `4h` | -| `PruneTxExpirationSecs` | Age threshold for pruning | `2h` | - ---- - -## **6. `bcs_util.go` (BCS Serialization Utilities)** -This module handles **BCS (Binary Canonical Serialization)** used in Aptos. - -### **Responsibilities:** -- Converts transaction parameters into **BCS format**. -- Ensures proper serialization for different data types. -- Provides helper functions for: - - **Type tag creation** (`CreateTypeTag()`) - - **Value serialization** (`CreateBcsValue()`) - -### **Key Methods:** -| Method | Description | -|--------|-------------| -| `CreateTypeTag()` | Converts a type string into an Aptos TypeTag. | -| `CreateBcsValue()` | Serializes a Go value into BCS format. | - ---- - -## **Transaction Lifecycle** -The following flowchart illustrates the lifecycle of a transaction in `AptosTxm`: - -```mermaid -sequenceDiagram - participant User - participant AptosTxm - participant TxStore - participant Aptos Blockchain - - User->>AptosTxm: Enqueue Transaction - AptosTxm->>TxStore: Store transaction details - AptosTxm->>AptosTxm: Broadcast transaction - AptosTxm->>Aptos Blockchain: Submit transaction - Aptos Blockchain-->>AptosTxm: Pending status - AptosTxm->>TxStore: Store as unconfirmed - loop Confirmation Loop - AptosTxm->>Aptos Blockchain: Check transaction status - Aptos Blockchain-->>AptosTxm: Confirmed or Failed - end - AptosTxm->>TxStore: Mark as finalized - AptosTxm->>User: Return status -``` - ---- - -## **Conclusion** -The **Aptos Transaction Manager (AptosTxm)** efficiently handles transaction management using a structured architecture: -- **`AptosTxm`** orchestrates transactions. -- **`TxStore` & `AccountStore`** manage nonce tracking and retries. -- **`BCS Utilities`** ensure Aptos-compatible serialization. -- **`Config`** allows fine-tuned performance settings. - -This design ensures **reliable transaction submission, nonce handling, retries, and confirmation tracking** in a scalable manner. 🚀 \ No newline at end of file diff --git a/relayer/txm/transaction_test.go b/relayer/txm/transaction_test.go index 3e074e90..ced2b809 100644 --- a/relayer/txm/transaction_test.go +++ b/relayer/txm/transaction_test.go @@ -11,7 +11,6 @@ import ( "testing" "github.com/block-vision/sui-go-sdk/models" - "github.com/block-vision/sui-go-sdk/sui" "github.com/block-vision/sui-go-sdk/transaction" "github.com/smartcontractkit/chainlink-common/pkg/logger" commontypes "github.com/smartcontractkit/chainlink-common/pkg/types" @@ -25,29 +24,6 @@ import ( "github.com/smartcontractkit/chainlink-sui/relayer/txm" ) -// SetupClients creates and configures Sui client and signer for testing. -// It generates a new key pair, creates a signer, and funds the signer address. -func SetupClients(t *testing.T, lggr logger.Logger) (rel.SuiSigner, sui.ISuiAPI) { - t.Helper() - - client := sui.NewSuiClient(testutils.LocalUrl) - - // Generate key pair and create a signer. - pk, _, _, err := testutils.GenerateAccountKeyPair(t) - require.NoError(t, err) - signer := rel.NewPrivateKeySigner(pk) - - // Fund the signer for contract deployment - signerAddress, err := signer.GetAddress() - require.NoError(t, err) - for range 3 { - err = testutils.FundWithFaucet(lggr, "localnet", signerAddress) - require.NoError(t, err) - } - - return signer, client -} - // TestTransactionGeneration tests the complete flow of generating and executing a Sui transaction // using PTBs. This integration test verifies: //