Skip to content

Instantly share code, notes, and snippets.

@Shaptic
Last active February 20, 2024 23:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Shaptic/5ce4f16d9cce7118f391fbde398c2f30 to your computer and use it in GitHub Desktop.
Save Shaptic/5ce4f16d9cce7118f391fbde398c2f30 to your computer and use it in GitHub Desktop.
A migration guide for stellar-sdk@v11.0.0

v11.0.0 Migration Guide

This update to the JavaScript SDK brings a lot of significant changes:

  • Support for Protocol 20, which introduces the Soroban smart contract platform to the Stellar network.
  • Support for Soroban RPC, a new web service unrelated to Horizon built around smart contract interactions.
  • A build system overhaul to Webpack 5 and ES6, meaning downstream systems will need to either transpile the library or update their build systems in turn in order to support the new bundles.
  • Tons of security fixes and performance optimizations that were enabled by the above, namely through updating all of the dependencies to their latest and greatest versions.

Traditionally, this SDK was a way to do two things:

  1. Build Stellar transactions, and
  2. interact with Horizon.

Because there is a new web service to interact with, there has been a significant overhaul in the structure of the modules in the SDK to properly support both Horizon and Soroban RPC. So now, we have:

  1. In stellar-sdk's default import, you can build Stellar transactions, and
  2. In stellar-sdk's Horizon namespace (or in stellar-sdk/horizon), you can interact with Horizon.
  3. In stellar-sdk's SorobanRpc namespace (or in stellar-sdk/soroban), you can interact with Soroban RPC.

Module Philosophy

There are now three primary namespaces in the SDK, each of which differs in responsibility depending on what you want to achieve:

  1. If you want to build operations, deal with accounts, or touch the XDR in any way (i.e. the fundamental building blocks of the Stellar protocol), you can use the package directly:
import * as StellarSdk from '@stellar/stellar-sdk';

In StellarSdk, you will find the Account and MuxedAccount objects, the TransactionBuilder, the SorobanDataBuilder, and all of the other "shared" concepts that simply define the Stellar protocol.

(If you've ever imported and used stellar-base directly, then this distinction should be clear: anything found in stellar-base lives in the top-level module.)

  1. If you want to interact with Horizon by querying its endpoints or submitting transactions, you will need to dig deeper into the module:
import { Horizon } from '@stellar/stellar-sdk';

In Horizon, you will find Server and all of the related "call builder" objects for its various endpoints. If you are interested in the API definitions themselves, then these will be in Horizon.HorizonApi or Horizon.ServerApi.

  1. If you want to interact with Soroban RPC, you will also need to dig deeper into the module in a very similar way:
import { SorobanRpc } from '@stellar/stellar-sdk';

This contains a different Server object which will let you execute various RPC endpoints. It also contains API schema definitions under the SorobanRpc.Api namespace, and misc. helpers for dealing with transaction simulation (e.g. SorobanRpc.assembleTransaction).

Upgrading from stellar-sdk

If you are upgrading from the latest stable version of the stellar-sdk package, you'll need to update your imports accordingly:

  • The package now lives under the @stellar/ npm namespace.
  • There is no longer a default export, so if you had import StellarSdk from 'stellar-sdk';, you will now need to write import * as StellarSdk from '@stellar/stellar-sdk';.
    • However, we highly recommend only importing the specific functionality that you need in order to keep your bundle sizes reasonable, e.g. import { TransactionBuilder, Account, Horizon } from '@stellar/stellar-sdk';.
  • Server now lives under the Horizon submodule, so you can either import { Horizon } from '@stellar/stellar-sdk'; or const { Server } = StellarSdk.Horizon;.
  • The API definitions are also under the Horizon submodule, so update your references accordingly.
  • Anything related to SEP-10 that used to live under the Utils namespace now lives under the WebAuth module. So if, before, you did StellarSdk.Utils.verifyTxSignedBy(...) you would now import { WebAuth } from '@stellar/stellar-sdk'; and call WebAuth.verifyTxSignedBy(...). Alternatively, you can grab members from the top-level module, e.g. const { verifyTxSignedBy } = StellarSdk.WebAuth;.
  • The TOML resolver and federation server have also moved under a separate module: StellarSdk.StellarToml contains a Resolver and StellarSdk.Federation contains a Server, respectively.

Future versions of the SDK are likely to move these latter two bullet points into separate packages to avoid bloating the bundle with features that most people do not need.

Here's an example before-and-after set of scripts:

Before:

import StellarSdk from 'stellar-sdk';

const s = StellarSdk.Server('https://horizon-testnet.stellar.org');
const kp = StellarSdk.Keypair.random();

async function main() {
    return s.loadAccount(kp.publicKey()).then(account => {
        const tx = new StellarSdk.TransactionBuilder(account, { fee: BASE_FEE })
            .addOperation(StellarSdk.Operation.payment({ /* etc. */ }))
            .setNetworkPassphrase(StellarSdk.Networks.TESTNET)
            .setTimeout(30)
            .build();

        tx.sign(kp);
        return s.submitTransaction(tx);
    });
}

After:

import {
    Keypair,
    Networks,
    TransactionBuilder,
    Operation,
    Horizon
} from '@stellar/stellar-sdk';

const s = Horizon.Server('https://horizon-testnet.stellar.org');
const kp = Keypair.random();

async function main() {
    return s.loadAccount(kp.publicKey()).then(account => {
        const tx = new TransactionBuilder(account, { fee: BASE_FEE })
            .addOperation(Operation.payment({ /* etc. */ }))
            .setNetworkPassphrase(Networks.TESTNET)
            .setTimeout(30)
            .build();

        tx.sign(kp);
        return s.submitTransaction(tx);
    });
}

Migrating from soroban-client

If you are coming from soroban-client and want to adapt your codebase to this library (or perhaps you want to leverage Horizon in your app, as well), then you will need to identify where you are interacting with the RPC server vs. just interacting with the Stellar protocol. Namely,

  • Server now lives under the SorobanRpc submodule
  • Soroban helpers such as assembleTransaction also live under this submodule
  • The API definitions now live in the SorobanRpc.Api submodule
  • Helpers that are "shared" and have nothing to do with the RPC (like TransactionBuilder and ContractSpec) are still in the top level

Here's an example before-and-after set of scripts:

Before:

import * as SorobanClient from 'soroban-client';

const s = SorobanClient.Server('https://rpc-testnet.stellar.org');
const kp = SorobanClient.Keypair.random();
const c = new Contract("C...");

async function main() {
    return s.getAccount(kp.publicKey()).then(account => {
        const tx = new SorobanClient.TransactionBuilder(account, {
            fee: BASE_FEE
        })
            .addOperation(c.call("hello", SorobanClient.scValToNative("world")))
            .setNetworkPassphrase(SorobanClient.Networks.FUTURENET)
            .setTimeout(30)
            .build();

        let sim: SorobanClient.SorobanRpc.SimulateTransactionResult;
        sim = await s.simulateTransaction(tx);
        const readyTx = SorobanClient.assembleTransaction(tx, sim);

        readyTx.sign(kp);
        return s.submitTransaction(readyTx);
    });
}

After:

import {
    Keypair,
    Networks,
    TransactionBuilder,
    Operation,
    scValToNative,
    SorobanRpc
} from '@stellar/stellar-sdk';
const { Api, assembleTransaction } = SorobanRpc;

const s = SorobanRpc.Server('https://soroban-testnet.stellar.org');
const kp = Keypair.random();

async function main() {
    return s.getAccount(kp.publicKey()).then(account => {
        const tx = new TransactionBuilder(account, { fee: BASE_FEE })
            .addOperation(c.call("hello", scValToNative("world")))
            .setNetworkPassphrase(Networks.TESTNET)
            .setTimeout(30)
            .build();

        let sim: Api.SimulateTransactionResult;
        sim = await s.simulateTransaction(tx);
        const readyTx = assembleTransaction(tx, sim);
        readyTx.sign(kp);

        return s.submitTransaction(readyTx);
    });
}
@chadoh
Copy link

chadoh commented Nov 27, 2023

  1. In stellar-sdk's Horizon namespace (or in stellar-sdk/horizon), you can interact with Horizon.
  2. In stellar-sdk's SorobanRpc namespace (or in stellar-sdk/soroban), you can interact with Horizon.

Second one should be "you can interact with [SorobanRpc]"

@Shaptic
Copy link
Author

Shaptic commented Nov 27, 2023

nice catch, fixed 🙏

@zingzongzung
Copy link

zingzongzung commented Dec 7, 2023

On the After example of calling a transaction, the server object is being initialized with the Horizon URL.

Should be using the Soroban RPC URL Right ?

Edit: In version 11 beta6 the assembleTransaction takes only two parameters.

@Shaptic
Copy link
Author

Shaptic commented Dec 8, 2023

Great catches, thank you @zingzongzung! Fixed both.

@Ayoseun
Copy link

Ayoseun commented Jan 19, 2024

"There is no longer a default export, so if you had import StellarSdk from 'stellar-sdk';, you will now need to write import * as StellarSdk from '@stellar/stellar-sdk';.
However, we highly recommend only importing the specific functionality that you need in order to keep your bundle sizes reasonable, e.g. import { TransactionBuilder, Account, Horizon } from '@stellar/stellar-sdk';.
Server now lives under the Horizon submodule, so you can either import { Horizon } from '@stellar/stellar-sdk'; "

There are some errors here ,

stellar-sdk is actually called as import { TransactionBuilder, Account, Horizon } from 'stellar-sdk';. calling it as import { TransactionBuilder, Account, Horizon } from '@stellar/stellar-sdk';. will through error "Error: Cannot find module '@stellar/stellar-sdk'"

@Ayoseun
Copy link

Ayoseun commented Jan 19, 2024

After Migration call should be

           import {
           Keypair,
           Networks,
          TransactionBuilder,
          Operation,
           Horizon} from 'stellar-sdk';

           const s = new Horizon.Server('https://horizon-testnet.stellar.org');
          const kp = Keypair.random();

    async function main() {
       return s.loadAccount(kp.publicKey()).then(account => {
           const tx = new TransactionBuilder(account, { fee: BASE_FEE })
              .addOperation(Operation.payment({ /* etc. */ }))
              .setNetworkPassphrase(Networks.TESTNET)
              .setTimeout(30)
              .build();

            tx.sign(kp);
             return s.submitTransaction(tx);
             });
       }

@Shaptic
Copy link
Author

Shaptic commented Jan 19, 2024

@Ayoseun thank you! The package has actually been renamed: while it still exists at stellar-sdk, we preferred to publish it under the @stellar/ organizational umbrella, here: @stellar/stellar-sdk. You can migrate easily like so:

npm uninstall stellar-sdk
npm i @stellar/stellar-sdk

and migrate imports accordingly.

@Ayoseun
Copy link

Ayoseun commented Jan 19, 2024

Thanks, is stellar-hd-wallet affected as well

@Shaptic
Copy link
Author

Shaptic commented Jan 19, 2024

Which package are you referring to?

@Ayoseun
Copy link

Ayoseun commented Jan 19, 2024

const StellarHDWallet = require('stellar-hd-wallet'), "stellar-hd-wallet": "^0.0.10", This one please

@Shaptic
Copy link
Author

Shaptic commented Jan 19, 2024

It looks like that library isn't maintained by the SDF, but it also appears to only use the Keypair module, which should not be affected by this migration. Though it does reference stellar-base, so you may have better luck continuing to use stellar-sdk rather than @stellar/stellar-sdk to avoid bizarre dependency conflicts.

@Spxc
Copy link

Spxc commented Jan 28, 2024

@Shaptic
Hey, did an upgrade of the library, and now we're getting a weird error:

TypeError: XDR Read Error: source not specified

This is the code:

const s =  new Horizon.Server("...")
s.loadAccount(issuerPair.publicKey())
        .then(async (source) => {
            const transaction = new TransactionBuilder(source, { fee: BASE_FEE })
            .addOperation(Operation.payment({
                destination: destination,
                amount: parseFloat(amount).toFixed(7),
                asset: sendAsset
            })) 
            .setNetworkPassphrase(Networks.PUBLIC)
            .addMemo(memo)
            .setTimeout(TimeoutInfinite)
            .build()

            transaction.sign(issuerPair)
           return client().submitTransaction(transaction)
            
        })
        .then(data => {
            console.log(data)
            resolve(data)
        })

What could cause this issue?
We also tried this on a bare bones NodeJS project, using @stellar/stellar-sdk

Everything worked fine before the upgrade and changes, and never stumbled upon this error before. The source is correct when logged.

@Spxc
Copy link

Spxc commented Jan 30, 2024

upgrade

For anyone else encountering the same issue (relates to Soroban SDK as well), you need to disable Hermes for iOS and Android, and enable V8-runtime in the Android gradle build file.

Symptoms:
TypeError: XDR Read Error: source not specified returned when doing basic Stellar operations - iOS
No identifiers allowed directly after numeric literal when running the app on Android

iOS:
Open up your Podfile and set the following to false:

 - :hermes_enabled => flags[:hermes_enabled],
 + :hermes_enabled => false, 

Android:
Open android/app/build.gradle and add the following after buildTypes

packagingOptions {
        jniLibs {
            useLegacyPackaging true
        }
        exclude "**/libjsc.so"
}

We used this package react-native-v8 using theJIT version, which supports Intl. Follow the basic setup steps, in the link above to fully setup the v8-runtime for Android

@overcat
Copy link

overcat commented Feb 4, 2024

@Shaptic I think we should point out the change that BigNumber.DEBUG has been set to true(PR). Previously, BigNumber.DEBUG defaulted to true, so users could determine if a number is valid by using .isBigNumber(value). However, when BigNumber.DEBUG is set to true, an exception will be thrown here. If we don't mention it, users may only notice it when they encounter an exception.

Also, I'm not sure if setting BigNumber.DEBUG=true as a default for users is really a good idea. Users may wonder why they didn't set BigNumber.DEBUG to true but still enabled this feature.

I encountered this problem while upgrading ledger-live. Fortunately, there were some test cases that made me aware of this issue, many parts of ledger-live rely on BigNumber.DEBUG=false😵.

@Shaptic
Copy link
Author

Shaptic commented Feb 5, 2024

Thank you @overcat! This is a great callout - I did not realize this would affect BigNumber globally beyond its usage within the SDK.

@overcat
Copy link

overcat commented Feb 6, 2024

Thank you @overcat! This is a great callout - I did not realize this would affect BigNumber globally beyond its usage within the SDK.

Should we change this behavior? Let it not affect the global state? I have created an issue here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment