Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 105 additions & 31 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"@dfinity/utils": "^2.5.0",
"@ledgerhq/hw-transport-node-hid-noevents": "^6.3.0",
"@ledgerhq/hw-transport-webhid": "^6.27.1",
"@zondax/ledger-icp": "^0.6.0",
"@zondax/ledger-icp": "^3.2.6",
"chalk": "^4.1.2",
"commander": "^9.0.0",
"node-fetch": "^2.6.1",
Expand Down
30 changes: 26 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,18 @@ import {
import { CANDID_PARSER_VERSION, HOTKEY_PERMISSIONS } from "./constants";
import { AnonymousIdentity, Identity } from "@dfinity/agent";
import { SnsGovernanceCanister, SnsNeuronId } from "@dfinity/sns";
import { TokenAmountV2, fromNullable, toNullable } from "@dfinity/utils";
import {
TokenAmountV2,
fromNullable,
isNullish,
toNullable,
} from "@dfinity/utils";
import {
decodeIcrcAccount,
encodeIcrcAccount,
IcrcAccount,
IcrcLedgerCanister,
toTransferArg,
} from "@dfinity/ledger-icrc";
import chalk from "chalk";
import {
Expand Down Expand Up @@ -293,13 +300,23 @@ async function icrcGetBalance(
ok(`Account ${encodeIcrcAccount(account)} has balance ${balance} e8s`);
}

async function supportedLedgers() {
const identity = await getIdentity();
const supportedTokens = await identity.getSupportedTokens();
const ledgersTextInfo = supportedTokens.map(
(ledger) =>
`Token Symbol: ${ledger.tokenSymbol}, Canister Id: ${ledger.canisterId}, Decimals: ${ledger.decimals}`
);
ok(`Supported ledgers: ${ledgersTextInfo.join("\n")}`);
}

// TODO: Add support for subaccounts
async function icrcSendTokens({
canisterId = MAINNET_LEDGER_CANISTER_ID,
amount,
to,
}: {
amount: TokenAmountV2;
amount: bigint;
to: IcrcAccount;
canisterId: Principal;
}) {
Expand All @@ -319,7 +336,7 @@ async function icrcSendTokens({
owner: to.owner,
subaccount: toNullable(to.subaccount),
},
amount: amount.toE8s(),
amount: amount,
fee,
created_at_time: nowInBigIntNanoSeconds(),
});
Expand Down Expand Up @@ -820,6 +837,11 @@ async function main() {
)
.action((args) => run(() => icrcGetBalance(args.canisterId)))
)
.addCommand(
new Command("supported-tokens")
.description("Get supported tokens of the ledger device.")
.action((args) => run(supportedLedgers))
)
.addCommand(
new Command("transfer")
.description("Send tokens from the ICRC wallet to another account.")
Expand All @@ -836,7 +858,7 @@ async function main() {
.requiredOption(
"--amount <amount>",
"Amount to transfer in e8s",
tryParseE8s
tryParseBigInt
)
.action(({ to, amount, canisterId }) => {
run(() => icrcSendTokens({ to, amount, canisterId }));
Expand Down
40 changes: 33 additions & 7 deletions src/ledger/identity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
SignIdentity,
} from "@dfinity/agent";
import { Principal } from "@dfinity/principal";
import LedgerApp, { LedgerError, ResponseSign } from "@zondax/ledger-icp";
import LedgerApp, { ResponseSign, TokenInfo } from "@zondax/ledger-icp";
import { Secp256k1PublicKey } from "./secp256k1";

// @ts-ignore (no types are available)
Expand All @@ -18,6 +18,7 @@ import TransportNodeHidNoEvents from "@ledgerhq/hw-transport-node-hid-noevents";
// Add polyfill for `window.fetch` for agent-js to work.
// @ts-ignore (no types are available)
import fetch from "node-fetch";
import { isNullish, nonNullish } from "@dfinity/utils";
global.fetch = fetch;

/**
Expand Down Expand Up @@ -101,20 +102,20 @@ export class LedgerIdentity extends SignIdentity {
}
}
}

private static async _fetchPublicKeyFromDevice(
app: LedgerApp,
derivePath: string
): Promise<Secp256k1PublicKey> {
const resp = await app.getAddressAndPubKey(derivePath);
// @ts-ignore
if (resp.returnCode == 28161) {
// Code references: https://github.com/Zondax/ledger-js/blob/799b056c0ed40af06d375b2b6220c0316f272fe7/src/consts.ts#L31
if (resp.returnCode == 0x6e01) {
throw "Please open the Internet Computer app on your wallet and try again.";
} else if (resp.returnCode == LedgerError.TransactionRejected) {
} else if (resp.returnCode == 0x5515) {
throw "Ledger Wallet is locked. Unlock it and try again.";
// @ts-ignore
} else if (resp.returnCode == 65535) {
} else if (resp.returnCode == 0xffff) {
throw "Unable to fetch the public key. Please try again.";
} else if (isNullish(resp.publicKey)) {
throw "Public key not available. Please try again.";
}

// This type doesn't have the right fields in it, so we have to manually type it.
Expand Down Expand Up @@ -152,6 +153,17 @@ export class LedgerIdentity extends SignIdentity {
public async getVersion(): Promise<Version> {
return this._executeWithApp(async (app: LedgerApp) => {
const res = await app.getVersion();
if (
isNullish(res.major) ||
isNullish(res.minor) ||
isNullish(res.patch)
) {
throw new Error(
`A ledger error happened during version fetch:
Code: ${res.returnCode}
Message: ${JSON.stringify(res.errorMessage)}`
);
}
return {
major: res.major,
minor: res.minor,
Expand All @@ -160,6 +172,20 @@ export class LedgerIdentity extends SignIdentity {
});
}

public async getSupportedTokens(): Promise<TokenInfo[]> {
return this._executeWithApp(async (app: LedgerApp) => {
const res = await app.tokenRegistry();
if (nonNullish(res.tokenRegistry)) {
return res.tokenRegistry;
}
throw new Error(
`A ledger error happened during token registry fetch:
Code: ${res.returnCode}
Message: ${JSON.stringify(res.errorMessage)}`
);
});
}

public getPublicKey(): PublicKey {
return this._publicKey;
}
Expand Down