-
Notifications
You must be signed in to change notification settings - Fork 327
LLC:gist tx
@greweb edited this page Feb 14, 2023
·
2 revisions
We start a new project and add live-common and some helpers
yarn add @ledgerhq/live-common
yarn add react # somehow an implicit dep of live-common
Now we need a concrete implementation of a Transport to use the ledger device with. In our example we're going to do a Node.js script that works with USB, so we're just going to install these:
yarn add @ledgerhq/hw-transport-node-hid-noevents
We're all set up, let's write a script that send some bitcoin!
const { first, map, reduce, tap } = require("rxjs/operators");
const {
getCryptoCurrencyById,
formatCurrencyUnit,
parseCurrencyUnit,
setSupportedCurrencies,
} = require("@ledgerhq/live-common/lib/currencies/index");
const {
getCurrencyBridge,
getAccountBridge,
} = require("@ledgerhq/live-common/lib/bridge/index");
// our small example is a script that takes 3 params.
// example: node send.ts bitcoin bc1abc..def 0.001
if (!process.argv[4]) {
console.log(`Usage: currencyId recipient amount`);
process.exit(1);
}
const currencyId = process.argv[2]; // example "ethereum"
const currency = getCryptoCurrencyById(currencyId);
const recipient = process.argv[3]; // example "0.1234"
const amount = parseCurrencyUnit(currency.units[0], process.argv[4]);
// ^the amount is actually stored as a BigNumber of the value in "smallest unit" of the currency. (eg. in wei or in satoshi)
const deviceId = ""; // in HID case
//////////////////////////////////
// live-common requires some setup. usually we put that in a live-common-setup.js
const {
registerTransportModule,
} = require("@ledgerhq/live-common/lib/hw/index");
const TransportNodeHid =
require("@ledgerhq/hw-transport-node-hid-noevents").default;
// configure which coins to enable
setSupportedCurrencies([currencyId]);
// configure which transport are available
registerTransportModule({
id: "hid",
open: (devicePath) => TransportNodeHid.open(devicePath),
disconnect: () => Promise.resolve(),
});
/////////////////////////
async function main() {
///////////////////
// we scan the device to find an account with a balance
// currency bridge is the interface to scan accounts of the device
const currencyBridge = getCurrencyBridge(currency);
// some currency requires some data to be loaded (today it's not highly used but will be more and more)
const data = await currencyBridge.preload(currency);
if (data) {
currencyBridge.hydrate(currency, data);
}
// in our case, we don't need to paginate
const syncConfig = { paginationConfig: {} };
// NB scanAccountsOnDevice returns an observable but we'll just get the first account as a promise.
const scannedAccount = await currencyBridge
.scanAccounts({ currency, deviceId, syncConfig })
.pipe(
// there can be many accounts, for sake of example we take first non empty
first((e) => e.type === "discovered" && e.account.balance.gt(0)),
map((e) => e.account)
)
.toPromise();
// account bridge is the interface to sync and do transaction on our account
const accountBridge = getAccountBridge(scannedAccount);
// Minimal way to synchronize an account.
// NB: our scannedAccount is already sync in fact, this is just for the example
const account = await accountBridge
.sync(scannedAccount, syncConfig)
.pipe(reduce((a, f) => f(a), scannedAccount))
.toPromise();
console.log(`${account.name} new address: ${account.freshAddress}`);
console.log(
`with balance of ${formatCurrencyUnit(account.unit, account.balance)}`
);
////////////////////////////////////////////////////////////////////////
// We prepare a transaction
// we initialize a transaction object to work on a given account
// this bootstraps a JSON object with the right shape
let t = accountBridge.createTransaction(account);
// we update the transaction with the recipient and amount
// you can update as much field as needed and this may depends on each coin
// usually you can see this being documented through the Transaction type of each coin
// amount and recipient are part of the 'common' fields
// they can be updated separately too
t = accountBridge.updateTransaction(t, { amount, recipient });
// each time we update the fields, we need to "prepare" the transaction
// if we want to get any feedback that "the transaction is ready to be signed"
// or if we need to show the UI its errors
// prepareTransaction deal with asynchronous network calls typically necessary to updates fees information OR calculate any coin specific work.
t = await accountBridge.prepareTransaction(account, t);
// From a prepared transaction, we are able to get the status of the transaction
// The status contains all derived data or meta information (like calculated fees)
// but more importantly will contain all the warnings and errors for the UI to display
const status = await accountBridge.getTransactionStatus(account, t);
console.log({ status });
// example: status.warnings.recipient for Ethereum when you don't respect EIP55 checksum
// example: status.errors.amount if you don't have enough balance
// The important rule being: we can't broadcast the transaction if there are errors
const errors = Object.values(status.errors);
if (errors.length) {
throw errors[0];
}
// but if there are no error the contract of a coin is that
// it means the transaction is possible and ready to sign and broadcast
// We're good now, we can sign the transaction with the device
const signedOperation = await accountBridge
.signOperation({ account, transaction: t, deviceId })
.pipe(
// it is a stream of events where you can notify the UI with progress, etc..
tap((e) => console.log(e)), // log events
// there are many events. we just take the final signed
first((e) => e.type === "signed"),
map((e) => e.signedOperation)
)
.toPromise();
// we now have a signed operation, and notably a signature that we can then broadcast to the network
const operation = await accountBridge.broadcast({ account, signedOperation });
// the transaction is broadcasted!
// the resulting operation is an "optimistic" response that can be prepended to our account.operations[]
console.log("broadcasted", operation);
}
main();
- Ledger Live Desktop
- Ledger Live Mobile
-
Ledger Live Common
- Introduction
- Currency Models
- Currency Bridge
- Account
- Account Bridge
- apps
- appsCheckAllAppVersions
- ledger-live bot
- Canonical Ways to Investigate Bugs
- Coin Integration Introduction
- Countervalues
- Packages Duplicates
- Derivation
- Developing with CLI
- Developing
- Gist Firmware Update
- Gist Transaction
- Hardware Wallet Logic
- Socket
- Assorted tips
- Integration Tests
- Process
- Monorepository Migration Guide
- Issues, Workaround and Tricks
- Common CI Troubleshooting
- Create staging builds using the CI
- Deprecated