@ledgerhq/coin-internet_computer 1.9.0-nightly.7 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +20 -66
- package/lib/api/api.d.ts +11 -6
- package/lib/api/api.d.ts.map +1 -1
- package/lib/api/api.js +64 -84
- package/lib/api/api.js.map +1 -1
- package/lib/bridge/bridgeHelpers/account.d.ts.map +1 -1
- package/lib/bridge/bridgeHelpers/account.js +33 -52
- package/lib/bridge/bridgeHelpers/account.js.map +1 -1
- package/lib/bridge/bridgeHelpers/addresses.d.ts +6 -0
- package/lib/bridge/bridgeHelpers/addresses.d.ts.map +1 -1
- package/lib/bridge/bridgeHelpers/addresses.js +29 -1
- package/lib/bridge/bridgeHelpers/addresses.js.map +1 -1
- package/lib/bridge/bridgeHelpers/icpRosetta/index.d.ts +30 -0
- package/lib/bridge/bridgeHelpers/icpRosetta/index.d.ts.map +1 -0
- package/lib/bridge/bridgeHelpers/icpRosetta/index.js +88 -0
- package/lib/bridge/bridgeHelpers/icpRosetta/index.js.map +1 -0
- package/lib/bridge/bridgeHelpers/icpRosetta/types.d.ts +145 -0
- package/lib/bridge/bridgeHelpers/icpRosetta/types.d.ts.map +1 -0
- package/lib/bridge/bridgeHelpers/icpRosetta/types.js +3 -0
- package/lib/bridge/bridgeHelpers/icpRosetta/types.js.map +1 -0
- package/lib/bridge/bridgeHelpers/icpRosetta/utils.d.ts +17 -0
- package/lib/bridge/bridgeHelpers/icpRosetta/utils.d.ts.map +1 -0
- package/lib/bridge/bridgeHelpers/icpRosetta/utils.js +155 -0
- package/lib/bridge/bridgeHelpers/icpRosetta/utils.js.map +1 -0
- package/lib/bridge/broadcast.d.ts.map +1 -1
- package/lib/bridge/broadcast.js +6 -22
- package/lib/bridge/broadcast.js.map +1 -1
- package/lib/bridge/getTransactionStatus.d.ts.map +1 -1
- package/lib/bridge/getTransactionStatus.js +3 -4
- package/lib/bridge/getTransactionStatus.js.map +1 -1
- package/lib/bridge/prepareTransaction.d.ts.map +1 -1
- package/lib/bridge/prepareTransaction.js +1 -2
- package/lib/bridge/prepareTransaction.js.map +1 -1
- package/lib/bridge/signOperation.d.ts +2 -2
- package/lib/bridge/signOperation.d.ts.map +1 -1
- package/lib/bridge/signOperation.js +13 -44
- package/lib/bridge/signOperation.js.map +1 -1
- package/lib/consts.d.ts +0 -2
- package/lib/consts.d.ts.map +1 -1
- package/lib/consts.js +1 -8
- package/lib/consts.js.map +1 -1
- package/lib/hw-signMessage.d.ts.map +1 -1
- package/lib/hw-signMessage.js +2 -1
- package/lib/hw-signMessage.js.map +1 -1
- package/lib/types/signer.d.ts +1 -1
- package/lib/types/signer.d.ts.map +1 -1
- package/lib-es/api/api.d.ts +11 -6
- package/lib-es/api/api.d.ts.map +1 -1
- package/lib-es/api/api.js +60 -81
- package/lib-es/api/api.js.map +1 -1
- package/lib-es/bridge/bridgeHelpers/account.d.ts.map +1 -1
- package/lib-es/bridge/bridgeHelpers/account.js +33 -52
- package/lib-es/bridge/bridgeHelpers/account.js.map +1 -1
- package/lib-es/bridge/bridgeHelpers/addresses.d.ts +6 -0
- package/lib-es/bridge/bridgeHelpers/addresses.d.ts.map +1 -1
- package/lib-es/bridge/bridgeHelpers/addresses.js +23 -0
- package/lib-es/bridge/bridgeHelpers/addresses.js.map +1 -1
- package/lib-es/bridge/bridgeHelpers/icpRosetta/index.d.ts +30 -0
- package/lib-es/bridge/bridgeHelpers/icpRosetta/index.d.ts.map +1 -0
- package/lib-es/bridge/bridgeHelpers/icpRosetta/index.js +76 -0
- package/lib-es/bridge/bridgeHelpers/icpRosetta/index.js.map +1 -0
- package/lib-es/bridge/bridgeHelpers/icpRosetta/types.d.ts +145 -0
- package/lib-es/bridge/bridgeHelpers/icpRosetta/types.d.ts.map +1 -0
- package/lib-es/bridge/bridgeHelpers/icpRosetta/types.js +2 -0
- package/lib-es/bridge/bridgeHelpers/icpRosetta/types.js.map +1 -0
- package/lib-es/bridge/bridgeHelpers/icpRosetta/utils.d.ts +17 -0
- package/lib-es/bridge/bridgeHelpers/icpRosetta/utils.d.ts.map +1 -0
- package/lib-es/bridge/bridgeHelpers/icpRosetta/utils.js +123 -0
- package/lib-es/bridge/bridgeHelpers/icpRosetta/utils.js.map +1 -0
- package/lib-es/bridge/broadcast.d.ts.map +1 -1
- package/lib-es/bridge/broadcast.js +6 -19
- package/lib-es/bridge/broadcast.js.map +1 -1
- package/lib-es/bridge/getTransactionStatus.d.ts.map +1 -1
- package/lib-es/bridge/getTransactionStatus.js +1 -2
- package/lib-es/bridge/getTransactionStatus.js.map +1 -1
- package/lib-es/bridge/prepareTransaction.d.ts.map +1 -1
- package/lib-es/bridge/prepareTransaction.js +1 -2
- package/lib-es/bridge/prepareTransaction.js.map +1 -1
- package/lib-es/bridge/signOperation.d.ts +2 -2
- package/lib-es/bridge/signOperation.d.ts.map +1 -1
- package/lib-es/bridge/signOperation.js +13 -41
- package/lib-es/bridge/signOperation.js.map +1 -1
- package/lib-es/consts.d.ts +0 -2
- package/lib-es/consts.d.ts.map +1 -1
- package/lib-es/consts.js +0 -3
- package/lib-es/consts.js.map +1 -1
- package/lib-es/hw-signMessage.d.ts.map +1 -1
- package/lib-es/hw-signMessage.js +2 -1
- package/lib-es/hw-signMessage.js.map +1 -1
- package/lib-es/types/signer.d.ts +1 -1
- package/lib-es/types/signer.d.ts.map +1 -1
- package/package.json +13 -7
- package/src/api/api.ts +71 -126
- package/src/bridge/bridgeHelpers/account.ts +43 -70
- package/src/bridge/bridgeHelpers/addresses.ts +25 -0
- package/src/bridge/bridgeHelpers/icpRosetta/index.ts +154 -0
- package/src/bridge/bridgeHelpers/icpRosetta/types.ts +166 -0
- package/src/bridge/bridgeHelpers/icpRosetta/utils.ts +151 -0
- package/src/bridge/broadcast.ts +6 -31
- package/src/bridge/getTransactionStatus.ts +1 -2
- package/src/bridge/prepareTransaction.ts +1 -2
- package/src/bridge/signOperation.ts +18 -68
- package/src/consts.ts +0 -10
- package/src/hw-signMessage.ts +6 -1
- package/src/types/signer.ts +1 -1
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { Account } from "@ledgerhq/types-live";
|
|
2
|
+
import { Transaction } from "../../../types";
|
|
3
|
+
import { constructionInvoke, getICPRosettaNetworkIdentifier } from "../../../api";
|
|
4
|
+
import {
|
|
5
|
+
ICPRosettaConstructionCombineRequest,
|
|
6
|
+
ICPRosettaConstructionCombineResponse,
|
|
7
|
+
ICPRosettaConstructionHashRequest,
|
|
8
|
+
ICPRosettaConstructionHashResponse,
|
|
9
|
+
ICPRosettaConstructionPayloadsRequest,
|
|
10
|
+
ICPRosettaConstructionPayloadsResponse,
|
|
11
|
+
ICPRosettaConstructionSubmitRequest,
|
|
12
|
+
ICPRosettaConstructionSubmitResponse,
|
|
13
|
+
ICPRosettaConstructionDeriveRequest,
|
|
14
|
+
ICPRosettaConstructionDeriveResponse,
|
|
15
|
+
} from "./types";
|
|
16
|
+
import { ingressExpiry, generateOperations, generateSignaturesPayload } from "./utils";
|
|
17
|
+
import { Cbor } from "@dfinity/agent";
|
|
18
|
+
import { Principal } from "@dfinity/principal";
|
|
19
|
+
import BigNumber from "bignumber.js";
|
|
20
|
+
import { ICP_SEND_TXN_TYPE } from "../../../consts";
|
|
21
|
+
import { SignerContext } from "@ledgerhq/coin-framework/signer";
|
|
22
|
+
import { ICPSigner } from "../../../types";
|
|
23
|
+
|
|
24
|
+
export const getUnsignedTransaction = async (
|
|
25
|
+
transaction: Transaction,
|
|
26
|
+
account: Account,
|
|
27
|
+
): Promise<{
|
|
28
|
+
unsignedTxn: string;
|
|
29
|
+
payloads: ICPRosettaConstructionPayloadsResponse["payloads"];
|
|
30
|
+
}> => {
|
|
31
|
+
const ops = generateOperations(transaction, account);
|
|
32
|
+
const pubkeys = [
|
|
33
|
+
{
|
|
34
|
+
hex_bytes: account.xpub ?? "",
|
|
35
|
+
curve_type: "secp256k1",
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const reqOpts: ICPRosettaConstructionPayloadsRequest = {
|
|
40
|
+
...getICPRosettaNetworkIdentifier(),
|
|
41
|
+
operations: ops,
|
|
42
|
+
public_keys: pubkeys,
|
|
43
|
+
metadata: {
|
|
44
|
+
memo: parseInt(transaction.memo ?? "0"),
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
const { payloads, unsigned_transaction } = await constructionInvoke<
|
|
48
|
+
ICPRosettaConstructionPayloadsRequest,
|
|
49
|
+
ICPRosettaConstructionPayloadsResponse
|
|
50
|
+
>(reqOpts, "payloads");
|
|
51
|
+
|
|
52
|
+
return { unsignedTxn: unsigned_transaction, payloads };
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const signICPTransaction = async ({
|
|
56
|
+
signerContext,
|
|
57
|
+
deviceId,
|
|
58
|
+
unsignedTxn,
|
|
59
|
+
path,
|
|
60
|
+
payloads,
|
|
61
|
+
pubkey,
|
|
62
|
+
}: {
|
|
63
|
+
signerContext: SignerContext<ICPSigner>;
|
|
64
|
+
deviceId: string;
|
|
65
|
+
unsignedTxn: string;
|
|
66
|
+
path: string;
|
|
67
|
+
payloads: ICPRosettaConstructionPayloadsResponse["payloads"];
|
|
68
|
+
pubkey: string;
|
|
69
|
+
}): Promise<{
|
|
70
|
+
signatures: { txnSig: string; readSig: string };
|
|
71
|
+
signedTxn: string;
|
|
72
|
+
}> => {
|
|
73
|
+
const decodedTxn: any = Cbor.decode(Buffer.from(unsignedTxn, "hex"));
|
|
74
|
+
const txnReqFromCbor = decodedTxn.updates[0][1];
|
|
75
|
+
const expiry = new ingressExpiry(BigNumber(decodedTxn.ingress_expiries[0].toString()));
|
|
76
|
+
|
|
77
|
+
const submitReq = {
|
|
78
|
+
request_type: "call",
|
|
79
|
+
canister_id: Principal.fromUint8Array(txnReqFromCbor.canister_id),
|
|
80
|
+
method_name: txnReqFromCbor.method_name,
|
|
81
|
+
arg: txnReqFromCbor.arg,
|
|
82
|
+
sender: Principal.fromUint8Array(txnReqFromCbor.sender),
|
|
83
|
+
ingress_expiry: expiry,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const txnBlobToSign = Cbor.encode({
|
|
87
|
+
content: submitReq,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const { r } = await signerContext(deviceId, async signer => {
|
|
91
|
+
const r = await signer.sign(path, Buffer.from(txnBlobToSign), ICP_SEND_TXN_TYPE);
|
|
92
|
+
return { r };
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const result = {
|
|
96
|
+
signatures: {
|
|
97
|
+
readSig: "",
|
|
98
|
+
txnSig: Buffer.from(r.signatureRS ?? "").toString("hex"),
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const signaturesPayload = generateSignaturesPayload(result.signatures, payloads, pubkey);
|
|
103
|
+
|
|
104
|
+
const { signed_transaction: signedTxn } = await constructionInvoke<
|
|
105
|
+
ICPRosettaConstructionCombineRequest,
|
|
106
|
+
ICPRosettaConstructionCombineResponse
|
|
107
|
+
>(
|
|
108
|
+
{
|
|
109
|
+
...getICPRosettaNetworkIdentifier(),
|
|
110
|
+
signatures: signaturesPayload,
|
|
111
|
+
unsigned_transaction: unsignedTxn,
|
|
112
|
+
},
|
|
113
|
+
"combine",
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
return { ...result, signedTxn };
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export const getTxnMetadata = async (signedTxn: string): Promise<{ hash: string }> => {
|
|
120
|
+
const {
|
|
121
|
+
transaction_identifier: { hash },
|
|
122
|
+
} = await constructionInvoke<
|
|
123
|
+
ICPRosettaConstructionHashRequest,
|
|
124
|
+
ICPRosettaConstructionHashResponse
|
|
125
|
+
>({ ...getICPRosettaNetworkIdentifier(), signed_transaction: signedTxn }, "hash");
|
|
126
|
+
|
|
127
|
+
return { hash };
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export const getTxnExpirationDate = (_unsignedTxn: string): Date => {
|
|
131
|
+
return new Date();
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export const broadcastTxn = async (signedTxn: string) => {
|
|
135
|
+
await constructionInvoke<
|
|
136
|
+
ICPRosettaConstructionSubmitRequest,
|
|
137
|
+
ICPRosettaConstructionSubmitResponse
|
|
138
|
+
>({ ...getICPRosettaNetworkIdentifier(), signed_transaction: signedTxn }, "submit");
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export const deriveAddressFromPubkey = async (pubkey: string) => {
|
|
142
|
+
const res = await constructionInvoke<
|
|
143
|
+
ICPRosettaConstructionDeriveRequest,
|
|
144
|
+
ICPRosettaConstructionDeriveResponse
|
|
145
|
+
>(
|
|
146
|
+
{
|
|
147
|
+
...getICPRosettaNetworkIdentifier(),
|
|
148
|
+
public_key: { curve_type: "secp256k1", hex_bytes: pubkey },
|
|
149
|
+
},
|
|
150
|
+
"derive",
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
return res.account_identifier.address;
|
|
154
|
+
};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { SubmitResponse } from "@dfinity/agent";
|
|
2
|
+
|
|
3
|
+
export interface ICPRosettaBlockHeightResponse {
|
|
4
|
+
current_block_identifier: ICPRosettaBlockIdentifier;
|
|
5
|
+
current_block_timestamp: number;
|
|
6
|
+
genesis_block_identifier: ICPRosettaBlockIdentifier;
|
|
7
|
+
sync_status: ICPRosettaSyncstatus;
|
|
8
|
+
peers: any[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface ICPRosettaBlockIdentifier {
|
|
12
|
+
index: number;
|
|
13
|
+
hash: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ICPRosettaGetBalancesResponse {
|
|
17
|
+
block_identifier: ICPRosettaBlockIdentifier;
|
|
18
|
+
balances: ICPRosettaBalance[];
|
|
19
|
+
details?: { error_message: string };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface ICPRosettaBalance {
|
|
23
|
+
value: string;
|
|
24
|
+
currency: ICPRosettaCurrency;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface ICPRosettaCurrency {
|
|
28
|
+
symbol: string;
|
|
29
|
+
decimals: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface ICPRosettaSyncstatus {
|
|
33
|
+
current_index: number;
|
|
34
|
+
target_index: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ICPRosettaGetTxnsHistoryResponse {
|
|
38
|
+
transactions: {
|
|
39
|
+
block_identifier: ICPRosettaBlockIdentifier;
|
|
40
|
+
transaction: {
|
|
41
|
+
transaction_identifier: ICPRosettaTransactionidentifier;
|
|
42
|
+
operations: ICPRosettaICPRosettaOperation[];
|
|
43
|
+
metadata: {
|
|
44
|
+
block_height: number;
|
|
45
|
+
memo: number;
|
|
46
|
+
timestamp: number;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
}[];
|
|
50
|
+
total_count: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface ICPRosettaICPRosettaOperation {
|
|
54
|
+
operation_identifier: {
|
|
55
|
+
index: number;
|
|
56
|
+
};
|
|
57
|
+
type: string;
|
|
58
|
+
status?: string;
|
|
59
|
+
account: ICPRosettaAccountIdentifier;
|
|
60
|
+
amount: ICPRosettaBalance;
|
|
61
|
+
metadata?: {
|
|
62
|
+
block_index: number;
|
|
63
|
+
transaction_identifier: ICPRosettaTransactionidentifier;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface ICPRosettaConstructionPayloadsRequest {
|
|
68
|
+
network_identifier: ICPRosettaNetworkIdentifier;
|
|
69
|
+
operations: ICPRosettaICPRosettaOperation[];
|
|
70
|
+
metadata?: ICPRosettaMetadata;
|
|
71
|
+
public_keys: ICPRosettaPublickey[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface ICPRosettaPublickey {
|
|
75
|
+
hex_bytes: string;
|
|
76
|
+
curve_type: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface ICPRosettaMetadata {
|
|
80
|
+
memo?: number;
|
|
81
|
+
created_at?: number;
|
|
82
|
+
ingress_end?: number;
|
|
83
|
+
ingress_start?: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface ICPRosettaCurrency {
|
|
87
|
+
symbol: string;
|
|
88
|
+
decimals: number;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface ICPRosettaNetworkIdentifier {
|
|
92
|
+
blockchain: string;
|
|
93
|
+
network: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface ICPRosettaConstructionPayloadsResponse {
|
|
97
|
+
unsigned_transaction: string;
|
|
98
|
+
payloads: {
|
|
99
|
+
account_identifier: ICPRosettaAccountIdentifier;
|
|
100
|
+
hex_bytes: string;
|
|
101
|
+
signature_type: string;
|
|
102
|
+
}[];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
interface ICPRosettaAccountIdentifier {
|
|
106
|
+
address: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface ICPRosettaConstructionCombineRequest {
|
|
110
|
+
network_identifier: ICPRosettaNetworkIdentifier;
|
|
111
|
+
unsigned_transaction: string; // cbor
|
|
112
|
+
signatures: ICPRosettaSignature[];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface ICPRosettaSignature {
|
|
116
|
+
signing_payload: {
|
|
117
|
+
account_identifier: ICPRosettaAccountIdentifier;
|
|
118
|
+
hex_bytes: string;
|
|
119
|
+
signature_type: string;
|
|
120
|
+
};
|
|
121
|
+
public_key: ICPRosettaPublickey;
|
|
122
|
+
signature_type: string;
|
|
123
|
+
hex_bytes: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface ICPRosettaConstructionCombineResponse {
|
|
127
|
+
signed_transaction: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface ICPRosettaConstructionSubmitRequest {
|
|
131
|
+
network_identifier: ICPRosettaNetworkIdentifier;
|
|
132
|
+
signed_transaction: string;
|
|
133
|
+
}
|
|
134
|
+
export interface ICPRosettaConstructionSubmitResponse {
|
|
135
|
+
transaction_identifier: ICPRosettaTransactionidentifier;
|
|
136
|
+
metadata: {
|
|
137
|
+
operations: ICPRosettaICPRosettaOperation[];
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
interface ICPRosettaTransactionidentifier {
|
|
142
|
+
hash: string;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface ICPRosettaConstructionHashRequest {
|
|
146
|
+
network_identifier: ICPRosettaNetworkIdentifier;
|
|
147
|
+
signed_transaction: string;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface ICPRosettaConstructionHashResponse {
|
|
151
|
+
transaction_identifier: ICPRosettaTransactionidentifier;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface ICPRosettaBroadcastResult extends SubmitResponse {
|
|
155
|
+
txnHash?: string;
|
|
156
|
+
blockHeight?: string;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface ICPRosettaConstructionDeriveRequest {
|
|
160
|
+
network_identifier: ICPRosettaNetworkIdentifier;
|
|
161
|
+
public_key: ICPRosettaPublickey;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface ICPRosettaConstructionDeriveResponse {
|
|
165
|
+
account_identifier: ICPRosettaAccountIdentifier;
|
|
166
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import * as cbor from "simple-cbor";
|
|
2
|
+
import {
|
|
3
|
+
ICPRosettaConstructionCombineRequest,
|
|
4
|
+
ICPRosettaConstructionPayloadsResponse,
|
|
5
|
+
ICPRosettaICPRosettaOperation,
|
|
6
|
+
} from "./types";
|
|
7
|
+
import { PipeArrayBuffer } from "@dfinity/candid";
|
|
8
|
+
import BigInteger from "big-integer";
|
|
9
|
+
import { Transaction } from "../../../types";
|
|
10
|
+
import { Account } from "@ledgerhq/types-live";
|
|
11
|
+
import { getAddress } from "../addresses";
|
|
12
|
+
import BigNumber from "bignumber.js";
|
|
13
|
+
|
|
14
|
+
export const generateOperations = (
|
|
15
|
+
tr: Transaction,
|
|
16
|
+
a: Account,
|
|
17
|
+
): ICPRosettaICPRosettaOperation[] => {
|
|
18
|
+
const { address } = getAddress(a);
|
|
19
|
+
const currency = {
|
|
20
|
+
symbol: "ICP",
|
|
21
|
+
decimals: 8,
|
|
22
|
+
};
|
|
23
|
+
const type = "TRANSACTION";
|
|
24
|
+
const operations: ICPRosettaICPRosettaOperation[] = [];
|
|
25
|
+
operations.push({
|
|
26
|
+
operation_identifier: {
|
|
27
|
+
index: 0,
|
|
28
|
+
},
|
|
29
|
+
type,
|
|
30
|
+
account: {
|
|
31
|
+
address,
|
|
32
|
+
},
|
|
33
|
+
amount: {
|
|
34
|
+
value: `-${tr.amount}`,
|
|
35
|
+
currency,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
operations.push({
|
|
40
|
+
operation_identifier: {
|
|
41
|
+
index: 1,
|
|
42
|
+
},
|
|
43
|
+
type,
|
|
44
|
+
account: {
|
|
45
|
+
address: tr.recipient,
|
|
46
|
+
},
|
|
47
|
+
amount: {
|
|
48
|
+
value: `${tr.amount}`,
|
|
49
|
+
currency,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
operations.push({
|
|
54
|
+
operation_identifier: {
|
|
55
|
+
index: 2,
|
|
56
|
+
},
|
|
57
|
+
type: "FEE",
|
|
58
|
+
account: {
|
|
59
|
+
address,
|
|
60
|
+
},
|
|
61
|
+
amount: {
|
|
62
|
+
value: `-${tr.fees}`,
|
|
63
|
+
currency,
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return operations;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
function expiryEncode(val: BigNumber): ArrayBuffer {
|
|
71
|
+
let value = BigInteger(val.toString());
|
|
72
|
+
|
|
73
|
+
if (value < BigInteger(0)) {
|
|
74
|
+
throw new Error("Cannot leb encode negative values.");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const byteLength = (value === BigInteger(0) ? 0 : Math.ceil(Math.log2(Number(value)))) + 1;
|
|
78
|
+
const pipe = new PipeArrayBuffer(new ArrayBuffer(byteLength), 0);
|
|
79
|
+
// eslint-disable-next-line no-constant-condition
|
|
80
|
+
while (true) {
|
|
81
|
+
const i = Number(value.and(BigInteger(0x7f)));
|
|
82
|
+
value = value.divide(BigInteger(0x80));
|
|
83
|
+
if (value.eq(BigInteger(0))) {
|
|
84
|
+
pipe.write(new Uint8Array([i]));
|
|
85
|
+
break;
|
|
86
|
+
} else {
|
|
87
|
+
pipe.write(new Uint8Array([i | 0x80]));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return pipe.buffer;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export class ingressExpiry {
|
|
95
|
+
value: BigNumber;
|
|
96
|
+
|
|
97
|
+
constructor(value: BigNumber) {
|
|
98
|
+
// Use bigint because it can overflow the maximum number allowed in a double float.
|
|
99
|
+
this.value = value;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public toCBOR(): cbor.CborValue {
|
|
103
|
+
return cbor.value.u64(this.value.toString(16), 16);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
public toHash(): ArrayBuffer {
|
|
107
|
+
return expiryEncode(this.value);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export const generateSignaturesPayload = (
|
|
112
|
+
signs: { txnSig: string; readSig: string },
|
|
113
|
+
payloads: ICPRosettaConstructionPayloadsResponse["payloads"],
|
|
114
|
+
pubkey: string,
|
|
115
|
+
): ICPRosettaConstructionCombineRequest["signatures"] => {
|
|
116
|
+
const signatures: ICPRosettaConstructionCombineRequest["signatures"] = [];
|
|
117
|
+
const [txnPayload, readStatePayload] = payloads;
|
|
118
|
+
signatures.push({
|
|
119
|
+
signing_payload: {
|
|
120
|
+
account_identifier: {
|
|
121
|
+
address: txnPayload.account_identifier.address,
|
|
122
|
+
},
|
|
123
|
+
hex_bytes: txnPayload.hex_bytes,
|
|
124
|
+
signature_type: txnPayload.signature_type,
|
|
125
|
+
},
|
|
126
|
+
public_key: {
|
|
127
|
+
hex_bytes: pubkey,
|
|
128
|
+
curve_type: "secp256k1",
|
|
129
|
+
},
|
|
130
|
+
signature_type: "ecdsa",
|
|
131
|
+
hex_bytes: signs.txnSig,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
signatures.push({
|
|
135
|
+
signing_payload: {
|
|
136
|
+
account_identifier: {
|
|
137
|
+
address: readStatePayload.account_identifier.address,
|
|
138
|
+
},
|
|
139
|
+
hex_bytes: readStatePayload.hex_bytes,
|
|
140
|
+
signature_type: readStatePayload.signature_type,
|
|
141
|
+
},
|
|
142
|
+
public_key: {
|
|
143
|
+
hex_bytes: pubkey,
|
|
144
|
+
curve_type: "secp256k1",
|
|
145
|
+
},
|
|
146
|
+
signature_type: "ecdsa",
|
|
147
|
+
hex_bytes: signs.readSig,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return signatures;
|
|
151
|
+
};
|
package/src/bridge/broadcast.ts
CHANGED
|
@@ -1,40 +1,15 @@
|
|
|
1
1
|
import { AccountBridge } from "@ledgerhq/types-live";
|
|
2
|
-
import { broadcastTxn } from "
|
|
2
|
+
import { broadcastTxn } from "./bridgeHelpers/icpRosetta";
|
|
3
3
|
import { Transaction } from "../types";
|
|
4
|
-
import { log } from "@ledgerhq/logs";
|
|
5
|
-
import invariant from "invariant";
|
|
6
|
-
import { MAINNET_LEDGER_CANISTER_ID } from "../consts";
|
|
7
4
|
|
|
8
|
-
// Interface to structure raw data for broadcasting transactions
|
|
9
|
-
interface BroadcastRawData {
|
|
10
|
-
encodedSignedCallBlob: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// Type guard to validate rawData shape
|
|
14
|
-
function isBroadcastRawData(data: unknown): data is BroadcastRawData {
|
|
15
|
-
return (
|
|
16
|
-
typeof data === "object" &&
|
|
17
|
-
data !== null &&
|
|
18
|
-
"encodedSignedCallBlob" in data &&
|
|
19
|
-
typeof (data as any).encodedSignedCallBlob === "string"
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Main broadcast function for handling Internet Computer transactions
|
|
24
5
|
export const broadcast: AccountBridge<Transaction>["broadcast"] = async ({
|
|
25
|
-
signedOperation: {
|
|
6
|
+
signedOperation: { signature, operation },
|
|
26
7
|
}) => {
|
|
27
|
-
log("debug", "[broadcast]
|
|
8
|
+
// log("debug", "[broadcast] start fn");
|
|
28
9
|
|
|
29
|
-
|
|
30
|
-
invariant(isBroadcastRawData(rawData), "[ICP](broadcast) Invalid rawData format");
|
|
31
|
-
invariant(operation.extra, "[ICP](broadcast) Missing operation extra");
|
|
10
|
+
await broadcastTxn(signature);
|
|
32
11
|
|
|
33
|
-
|
|
34
|
-
Buffer.from(rawData.encodedSignedCallBlob, "hex"),
|
|
35
|
-
MAINNET_LEDGER_CANISTER_ID,
|
|
36
|
-
"call",
|
|
37
|
-
);
|
|
12
|
+
const result = { ...operation };
|
|
38
13
|
|
|
39
|
-
return
|
|
14
|
+
return result;
|
|
40
15
|
};
|
|
@@ -7,8 +7,7 @@ import {
|
|
|
7
7
|
} from "@ledgerhq/errors";
|
|
8
8
|
import BigNumber from "bignumber.js";
|
|
9
9
|
import { AccountBridge } from "@ledgerhq/types-live";
|
|
10
|
-
import { getAddress } from "./bridgeHelpers/addresses";
|
|
11
|
-
import { validateAddress, validateMemo } from "@zondax/ledger-live-icp/utils";
|
|
10
|
+
import { getAddress, validateAddress, validateMemo } from "./bridgeHelpers/addresses";
|
|
12
11
|
import { Transaction, TransactionStatus } from "../types";
|
|
13
12
|
import { InvalidMemoICP } from "../errors";
|
|
14
13
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { AccountBridge } from "@ledgerhq/types-live";
|
|
2
2
|
import { Transaction } from "../types";
|
|
3
|
-
import { getAddress } from "./bridgeHelpers/addresses";
|
|
4
|
-
import { validateAddress } from "@zondax/ledger-live-icp";
|
|
3
|
+
import { getAddress, validateAddress } from "./bridgeHelpers/addresses";
|
|
5
4
|
|
|
6
5
|
export const prepareTransaction: AccountBridge<Transaction>["prepareTransaction"] = async (
|
|
7
6
|
account,
|
|
@@ -1,103 +1,53 @@
|
|
|
1
1
|
import { Observable } from "rxjs";
|
|
2
|
-
import { Account, AccountBridge
|
|
2
|
+
import { Account, AccountBridge } from "@ledgerhq/types-live";
|
|
3
3
|
import { getAddress } from "./bridgeHelpers/addresses";
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from "
|
|
10
|
-
import { Cbor } from "@zondax/ledger-live-icp/agent";
|
|
5
|
+
getTxnExpirationDate,
|
|
6
|
+
getTxnMetadata,
|
|
7
|
+
getUnsignedTransaction,
|
|
8
|
+
signICPTransaction,
|
|
9
|
+
} from "./bridgeHelpers/icpRosetta";
|
|
11
10
|
import { buildOptimisticOperation } from "./buildOptimisticOperation";
|
|
12
11
|
import { Transaction } from "../types";
|
|
13
12
|
import { SignerContext } from "@ledgerhq/coin-framework/signer";
|
|
14
13
|
import { ICPSigner } from "../types";
|
|
15
14
|
import { getPath } from "../common-logic";
|
|
16
|
-
import { log } from "@ledgerhq/logs";
|
|
17
|
-
import invariant from "invariant";
|
|
18
|
-
|
|
19
|
-
const signICPTransaction = async (
|
|
20
|
-
unsignedTxn: UnsignedTransaction,
|
|
21
|
-
derivationPath: string,
|
|
22
|
-
signerContext: SignerContext<ICPSigner>,
|
|
23
|
-
account: Account,
|
|
24
|
-
deviceId: DeviceId,
|
|
25
|
-
) => {
|
|
26
|
-
const blob = Cbor.encode({ content: unsignedTxn });
|
|
27
|
-
log("debug", "[signICPTransaction] blob", Buffer.from(blob).toString("hex"));
|
|
28
|
-
const signatures = await signerContext(deviceId, signer =>
|
|
29
|
-
signer.sign(derivationPath, Buffer.from(blob)),
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
invariant(signatures.signatureRS, "[ICP](signICPTransaction) Signature not found");
|
|
33
|
-
invariant(account.xpub, "[ICP](signICPTransaction) Account xpub is required");
|
|
34
|
-
return {
|
|
35
|
-
signature: Buffer.from(signatures.signatureRS).toString("hex"),
|
|
36
|
-
callBody: {
|
|
37
|
-
content: unsignedTxn,
|
|
38
|
-
sender_pubkey: pubkeyToDer(account.xpub),
|
|
39
|
-
sender_sig: signatures.signatureRS,
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
};
|
|
43
15
|
|
|
44
16
|
export const buildSignOperation =
|
|
45
|
-
(signerContext: SignerContext<ICPSigner>): AccountBridge<Transaction>["signOperation"] =>
|
|
17
|
+
(signerContext: SignerContext<ICPSigner>): AccountBridge<Transaction, Account>["signOperation"] =>
|
|
46
18
|
({ account, transaction, deviceId }) =>
|
|
47
19
|
new Observable(o => {
|
|
48
20
|
async function main() {
|
|
49
|
-
log("debug", "[signOperation] icp start fn");
|
|
50
|
-
log("debug", "[signOperation] transaction", transaction);
|
|
51
|
-
|
|
52
21
|
const { xpub } = account;
|
|
53
|
-
invariant(xpub, "[ICP](signOperation) Account xpub is required");
|
|
54
|
-
|
|
55
22
|
const { derivationPath } = getAddress(account);
|
|
56
|
-
|
|
57
|
-
const { unsignedTransaction, transferRawRequest } = createUnsignedSendTransaction(
|
|
58
|
-
transaction,
|
|
59
|
-
xpub,
|
|
60
|
-
);
|
|
23
|
+
const { unsignedTxn, payloads } = await getUnsignedTransaction(transaction, account);
|
|
61
24
|
|
|
62
25
|
o.next({
|
|
63
26
|
type: "device-signature-requested",
|
|
64
27
|
});
|
|
65
28
|
|
|
66
|
-
|
|
67
|
-
let encodedSignedCallBlob: string = "";
|
|
68
|
-
const res = await signICPTransaction(
|
|
69
|
-
unsignedTransaction,
|
|
70
|
-
getPath(derivationPath),
|
|
29
|
+
const { signedTxn } = await signICPTransaction({
|
|
71
30
|
signerContext,
|
|
72
|
-
account,
|
|
73
31
|
deviceId,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
32
|
+
unsignedTxn,
|
|
33
|
+
path: getPath(derivationPath),
|
|
34
|
+
payloads,
|
|
35
|
+
pubkey: xpub ?? "",
|
|
36
|
+
});
|
|
78
37
|
|
|
79
38
|
o.next({
|
|
80
39
|
type: "device-signature-granted",
|
|
81
40
|
});
|
|
82
41
|
|
|
83
|
-
const hash =
|
|
84
|
-
from: account.freshAddress,
|
|
85
|
-
to: transaction.recipient,
|
|
86
|
-
amount: transferRawRequest.amount.e8s,
|
|
87
|
-
fee: transferRawRequest.fee.e8s,
|
|
88
|
-
memo: transferRawRequest.memo,
|
|
89
|
-
created_at_time: transferRawRequest.created_at_time[0]["timestamp_nanos"],
|
|
90
|
-
});
|
|
91
|
-
|
|
42
|
+
const { hash } = await getTxnMetadata(signedTxn);
|
|
92
43
|
const operation = await buildOptimisticOperation(account, transaction, hash);
|
|
44
|
+
|
|
93
45
|
o.next({
|
|
94
46
|
type: "signed",
|
|
95
47
|
signedOperation: {
|
|
96
48
|
operation,
|
|
97
|
-
signature,
|
|
98
|
-
|
|
99
|
-
encodedSignedCallBlob,
|
|
100
|
-
},
|
|
49
|
+
signature: signedTxn,
|
|
50
|
+
expirationDate: getTxnExpirationDate(unsignedTxn),
|
|
101
51
|
},
|
|
102
52
|
});
|
|
103
53
|
}
|
package/src/consts.ts
CHANGED
|
@@ -9,13 +9,3 @@ export const ICP_FEES = 1e4;
|
|
|
9
9
|
|
|
10
10
|
// Max Memo value on ICP network
|
|
11
11
|
export const MAX_MEMO_VALUE = Number.MAX_SAFE_INTEGER;
|
|
12
|
-
|
|
13
|
-
// API limits
|
|
14
|
-
export const FETCH_TXNS_LIMIT = 100;
|
|
15
|
-
|
|
16
|
-
export {
|
|
17
|
-
MAINNET_GOVERNANCE_CANISTER_ID,
|
|
18
|
-
MAINNET_LEDGER_CANISTER_ID,
|
|
19
|
-
MAINNET_INDEX_CANISTER_ID,
|
|
20
|
-
ICP_NETWORK_URL,
|
|
21
|
-
} from "@zondax/ledger-live-icp/neurons";
|
package/src/hw-signMessage.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { log } from "@ledgerhq/logs";
|
|
2
2
|
import { getBufferFromString } from "./common-logic/utils";
|
|
3
|
+
import { ICP_SEND_TXN_TYPE } from "./consts";
|
|
3
4
|
import { SignerContext } from "@ledgerhq/coin-framework/signer";
|
|
4
5
|
import { ICPSigner } from "./types";
|
|
5
6
|
import { Account, AnyMessage } from "@ledgerhq/types-live";
|
|
@@ -18,7 +19,11 @@ export const signMessage =
|
|
|
18
19
|
if (typeof message !== "string") throw new Error("Message must be a string");
|
|
19
20
|
|
|
20
21
|
const { r } = await signerContext(deviceId, async signer => {
|
|
21
|
-
const r = await signer.sign(
|
|
22
|
+
const r = await signer.sign(
|
|
23
|
+
account.freshAddressPath,
|
|
24
|
+
getBufferFromString(message),
|
|
25
|
+
ICP_SEND_TXN_TYPE,
|
|
26
|
+
);
|
|
22
27
|
return { r };
|
|
23
28
|
});
|
|
24
29
|
|