@ledgerhq/coin-canton 0.5.0-nightly.1 → 0.5.0-nightly.2
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/.unimportedrc.json +12 -4
- package/CHANGELOG.md +21 -0
- package/lib/api/getBalance.integ.test.js +1 -1
- package/lib/api/getBalance.integ.test.js.map +1 -1
- package/lib/api/index.d.ts.map +1 -1
- package/lib/api/index.js +11 -8
- package/lib/api/index.js.map +1 -1
- package/lib/bridge/createTransaction.test.js +1 -1
- package/lib/bridge/createTransaction.test.js.map +1 -1
- package/lib/bridge/index.d.ts +3 -3
- package/lib/bridge/index.d.ts.map +1 -1
- package/lib/bridge/index.js +9 -1
- package/lib/bridge/index.js.map +1 -1
- package/lib/bridge/onboard.d.ts +10 -0
- package/lib/bridge/onboard.d.ts.map +1 -0
- package/lib/bridge/onboard.integ.test.d.ts +2 -0
- package/lib/bridge/onboard.integ.test.d.ts.map +1 -0
- package/lib/bridge/onboard.integ.test.js +156 -0
- package/lib/bridge/onboard.integ.test.js.map +1 -0
- package/lib/bridge/onboard.js +139 -0
- package/lib/bridge/onboard.js.map +1 -0
- package/lib/bridge/prepareTransaction.d.ts.map +1 -1
- package/lib/bridge/prepareTransaction.js +5 -7
- package/lib/bridge/prepareTransaction.js.map +1 -1
- package/lib/bridge/signOperation.d.ts.map +1 -1
- package/lib/bridge/signOperation.js +2 -1
- package/lib/bridge/signOperation.js.map +1 -1
- package/lib/bridge/sync.d.ts.map +1 -1
- package/lib/bridge/sync.integ.test.d.ts +2 -0
- package/lib/bridge/sync.integ.test.d.ts.map +1 -0
- package/lib/bridge/sync.integ.test.js +175 -0
- package/lib/bridge/sync.integ.test.js.map +1 -0
- package/lib/bridge/sync.js +39 -36
- package/lib/bridge/sync.js.map +1 -1
- package/lib/bridge/updateTransaction.d.ts.map +1 -1
- package/lib/bridge/updateTransaction.js +0 -4
- package/lib/bridge/updateTransaction.js.map +1 -1
- package/lib/common-logic/history/listOperations.d.ts.map +1 -1
- package/lib/common-logic/history/listOperations.js +19 -31
- package/lib/common-logic/history/listOperations.js.map +1 -1
- package/lib/common-logic/transaction/craftTransaction.d.ts +4 -3
- package/lib/common-logic/transaction/craftTransaction.d.ts.map +1 -1
- package/lib/common-logic/transaction/craftTransaction.js +10 -12
- package/lib/common-logic/transaction/craftTransaction.js.map +1 -1
- package/lib/network/gateway.d.ts +194 -3
- package/lib/network/gateway.d.ts.map +1 -1
- package/lib/network/gateway.integ.test.js +121 -11
- package/lib/network/gateway.integ.test.js.map +1 -1
- package/lib/network/gateway.js +96 -18
- package/lib/network/gateway.js.map +1 -1
- package/lib/network/node.d.ts +2 -2
- package/lib/network/node.d.ts.map +1 -1
- package/lib/network/node.js.map +1 -1
- package/lib/network/types.d.ts +1 -1
- package/lib/network/types.d.ts.map +1 -1
- package/lib/signer/getAddress.d.ts.map +1 -1
- package/lib/signer/getAddress.js +2 -2
- package/lib/signer/getAddress.js.map +1 -1
- package/lib/test/cantonTestUtils.d.ts +33 -0
- package/lib/test/cantonTestUtils.d.ts.map +1 -0
- package/lib/test/cantonTestUtils.js +159 -0
- package/lib/test/cantonTestUtils.js.map +1 -0
- package/lib/types/bridge.d.ts +7 -1
- package/lib/types/bridge.d.ts.map +1 -1
- package/lib/types/index.d.ts +1 -10
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +1 -0
- package/lib/types/index.js.map +1 -1
- package/lib/types/onboard.d.ts +55 -0
- package/lib/types/onboard.d.ts.map +1 -0
- package/lib/types/onboard.js +22 -0
- package/lib/types/onboard.js.map +1 -0
- package/lib-es/api/getBalance.integ.test.js +1 -1
- package/lib-es/api/getBalance.integ.test.js.map +1 -1
- package/lib-es/api/index.d.ts.map +1 -1
- package/lib-es/api/index.js +12 -9
- package/lib-es/api/index.js.map +1 -1
- package/lib-es/bridge/createTransaction.test.js +1 -1
- package/lib-es/bridge/createTransaction.test.js.map +1 -1
- package/lib-es/bridge/index.d.ts +3 -3
- package/lib-es/bridge/index.d.ts.map +1 -1
- package/lib-es/bridge/index.js +9 -1
- package/lib-es/bridge/index.js.map +1 -1
- package/lib-es/bridge/onboard.d.ts +10 -0
- package/lib-es/bridge/onboard.d.ts.map +1 -0
- package/lib-es/bridge/onboard.integ.test.d.ts +2 -0
- package/lib-es/bridge/onboard.integ.test.d.ts.map +1 -0
- package/lib-es/bridge/onboard.integ.test.js +151 -0
- package/lib-es/bridge/onboard.integ.test.js.map +1 -0
- package/lib-es/bridge/onboard.js +133 -0
- package/lib-es/bridge/onboard.js.map +1 -0
- package/lib-es/bridge/prepareTransaction.d.ts.map +1 -1
- package/lib-es/bridge/prepareTransaction.js +6 -8
- package/lib-es/bridge/prepareTransaction.js.map +1 -1
- package/lib-es/bridge/signOperation.d.ts.map +1 -1
- package/lib-es/bridge/signOperation.js +2 -1
- package/lib-es/bridge/signOperation.js.map +1 -1
- package/lib-es/bridge/sync.d.ts.map +1 -1
- package/lib-es/bridge/sync.integ.test.d.ts +2 -0
- package/lib-es/bridge/sync.integ.test.d.ts.map +1 -0
- package/lib-es/bridge/sync.integ.test.js +137 -0
- package/lib-es/bridge/sync.integ.test.js.map +1 -0
- package/lib-es/bridge/sync.js +38 -35
- package/lib-es/bridge/sync.js.map +1 -1
- package/lib-es/bridge/updateTransaction.d.ts.map +1 -1
- package/lib-es/bridge/updateTransaction.js +0 -4
- package/lib-es/bridge/updateTransaction.js.map +1 -1
- package/lib-es/common-logic/history/listOperations.d.ts.map +1 -1
- package/lib-es/common-logic/history/listOperations.js +20 -29
- package/lib-es/common-logic/history/listOperations.js.map +1 -1
- package/lib-es/common-logic/transaction/craftTransaction.d.ts +4 -3
- package/lib-es/common-logic/transaction/craftTransaction.d.ts.map +1 -1
- package/lib-es/common-logic/transaction/craftTransaction.js +10 -12
- package/lib-es/common-logic/transaction/craftTransaction.js.map +1 -1
- package/lib-es/network/gateway.d.ts +194 -3
- package/lib-es/network/gateway.d.ts.map +1 -1
- package/lib-es/network/gateway.integ.test.js +122 -12
- package/lib-es/network/gateway.integ.test.js.map +1 -1
- package/lib-es/network/gateway.js +90 -17
- package/lib-es/network/gateway.js.map +1 -1
- package/lib-es/network/node.d.ts +2 -2
- package/lib-es/network/node.d.ts.map +1 -1
- package/lib-es/network/node.js.map +1 -1
- package/lib-es/network/types.d.ts +1 -1
- package/lib-es/network/types.d.ts.map +1 -1
- package/lib-es/signer/getAddress.d.ts.map +1 -1
- package/lib-es/signer/getAddress.js +2 -2
- package/lib-es/signer/getAddress.js.map +1 -1
- package/lib-es/test/cantonTestUtils.d.ts +33 -0
- package/lib-es/test/cantonTestUtils.d.ts.map +1 -0
- package/lib-es/test/cantonTestUtils.js +151 -0
- package/lib-es/test/cantonTestUtils.js.map +1 -0
- package/lib-es/types/bridge.d.ts +7 -1
- package/lib-es/types/bridge.d.ts.map +1 -1
- package/lib-es/types/index.d.ts +1 -10
- package/lib-es/types/index.d.ts.map +1 -1
- package/lib-es/types/index.js +1 -0
- package/lib-es/types/index.js.map +1 -1
- package/lib-es/types/onboard.d.ts +55 -0
- package/lib-es/types/onboard.d.ts.map +1 -0
- package/lib-es/types/onboard.js +19 -0
- package/lib-es/types/onboard.js.map +1 -0
- package/package.json +6 -6
- package/src/api/getBalance.integ.test.ts +1 -2
- package/src/api/index.ts +33 -26
- package/src/bridge/createTransaction.test.ts +1 -1
- package/src/bridge/index.ts +14 -4
- package/src/bridge/onboard.integ.test.ts +219 -0
- package/src/bridge/onboard.ts +220 -0
- package/src/bridge/prepareTransaction.ts +6 -15
- package/src/bridge/signOperation.ts +3 -2
- package/src/bridge/sync.integ.test.ts +180 -0
- package/src/bridge/sync.ts +57 -49
- package/src/bridge/updateTransaction.ts +0 -5
- package/src/common-logic/history/listOperations.ts +20 -31
- package/src/common-logic/transaction/craftTransaction.ts +13 -17
- package/src/network/gateway.integ.test.ts +156 -17
- package/src/network/gateway.ts +333 -26
- package/src/network/node.ts +3 -3
- package/src/network/types.ts +1 -1
- package/src/signer/getAddress.ts +3 -5
- package/src/test/cantonTestUtils.ts +181 -0
- package/src/types/bridge.ts +20 -0
- package/src/types/index.ts +1 -11
- package/src/types/onboard.ts +65 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { Observable } from "rxjs";
|
|
2
|
+
import { SignerContext } from "@ledgerhq/coin-framework/signer";
|
|
3
|
+
import { CantonSigner } from "../types/signer";
|
|
4
|
+
import {
|
|
5
|
+
prepareOnboarding,
|
|
6
|
+
submitOnboarding,
|
|
7
|
+
getPartyByPubKey,
|
|
8
|
+
preparePreApprovalTransaction,
|
|
9
|
+
submitPreApprovalTransaction,
|
|
10
|
+
prepareTapRequest,
|
|
11
|
+
submitTapRequest,
|
|
12
|
+
} from "../network/gateway";
|
|
13
|
+
import {
|
|
14
|
+
OnboardStatus,
|
|
15
|
+
PreApprovalStatus,
|
|
16
|
+
CantonOnboardProgress,
|
|
17
|
+
CantonOnboardResult,
|
|
18
|
+
CantonPreApprovalProgress,
|
|
19
|
+
CantonPreApprovalResult,
|
|
20
|
+
PrepareTransactionResponse,
|
|
21
|
+
} from "../types/onboard";
|
|
22
|
+
|
|
23
|
+
async function getKeypair(
|
|
24
|
+
signerContext: SignerContext<CantonSigner>,
|
|
25
|
+
deviceId: string,
|
|
26
|
+
derivationPath: string,
|
|
27
|
+
) {
|
|
28
|
+
return signerContext(deviceId, async signer => {
|
|
29
|
+
const { publicKey, address } = await signer.getAddress(derivationPath);
|
|
30
|
+
return { signer, publicKey: publicKey.replace("0x", ""), address };
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const isAccountOnboarded = async (
|
|
35
|
+
publicKey: string,
|
|
36
|
+
): Promise<false | { party_id: string }> => {
|
|
37
|
+
try {
|
|
38
|
+
const { party_id } = await getPartyByPubKey(publicKey);
|
|
39
|
+
|
|
40
|
+
if (party_id) {
|
|
41
|
+
return { party_id };
|
|
42
|
+
} else {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
} catch (err) {
|
|
46
|
+
log("[isAccountOnboarded] Error checking party status (likely not onboarded):", err);
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const buildOnboardAccount =
|
|
52
|
+
(signerContext: SignerContext<CantonSigner>) =>
|
|
53
|
+
(
|
|
54
|
+
deviceId: string,
|
|
55
|
+
derivationPath: string,
|
|
56
|
+
): Observable<CantonOnboardProgress | CantonOnboardResult> =>
|
|
57
|
+
new Observable(observer => {
|
|
58
|
+
async function main() {
|
|
59
|
+
observer.next({
|
|
60
|
+
status: OnboardStatus.INIT,
|
|
61
|
+
});
|
|
62
|
+
const keypair = await getKeypair(signerContext, deviceId, derivationPath);
|
|
63
|
+
|
|
64
|
+
observer.next({
|
|
65
|
+
status: OnboardStatus.PREPARE,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const isOnboardedResult = await isAccountOnboarded(keypair.publicKey);
|
|
69
|
+
if (isOnboardedResult && isOnboardedResult.party_id) {
|
|
70
|
+
observer.next({
|
|
71
|
+
partyId: isOnboardedResult.party_id,
|
|
72
|
+
});
|
|
73
|
+
observer.complete();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const preparedTransaction = await prepareOnboarding(keypair.publicKey, "ed25519");
|
|
78
|
+
|
|
79
|
+
observer.next({
|
|
80
|
+
status: OnboardStatus.SIGN,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const signature = await signerContext(deviceId, signer =>
|
|
84
|
+
signer.signTransaction(derivationPath, preparedTransaction.transactions.combined_hash),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
observer.next({
|
|
88
|
+
status: OnboardStatus.SUBMIT,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const result = await submitOnboarding(
|
|
92
|
+
{ public_key: keypair.publicKey, public_key_type: "ed25519" },
|
|
93
|
+
preparedTransaction,
|
|
94
|
+
signature,
|
|
95
|
+
).catch(err => {
|
|
96
|
+
if (err.type === "PARTY_ALREADY_EXISTS") {
|
|
97
|
+
observer.next({
|
|
98
|
+
partyId: preparedTransaction.party_id,
|
|
99
|
+
});
|
|
100
|
+
return observer.complete();
|
|
101
|
+
}
|
|
102
|
+
throw err;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
observer.next({
|
|
106
|
+
status: OnboardStatus.SUCCESS,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
observer.next({
|
|
110
|
+
partyId: result?.party?.party_id || "unknown",
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
observer.complete();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
main().then(
|
|
117
|
+
() => observer.complete(),
|
|
118
|
+
error => {
|
|
119
|
+
log("[onboardAccount] Error:", error);
|
|
120
|
+
observer.error(error);
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
export const buildAuthorizePreapproval =
|
|
126
|
+
(signerContext: SignerContext<CantonSigner>) =>
|
|
127
|
+
(
|
|
128
|
+
deviceId: string,
|
|
129
|
+
derivationPath: string,
|
|
130
|
+
partyId: string,
|
|
131
|
+
): Observable<CantonPreApprovalProgress | CantonPreApprovalResult> =>
|
|
132
|
+
new Observable(observer => {
|
|
133
|
+
async function main() {
|
|
134
|
+
observer.next({
|
|
135
|
+
status: PreApprovalStatus.PREPARE,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const preparedTransaction: PrepareTransactionResponse =
|
|
139
|
+
await preparePreApprovalTransaction(partyId);
|
|
140
|
+
|
|
141
|
+
observer.next({
|
|
142
|
+
status: PreApprovalStatus.SIGN,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const signature = await signerContext(deviceId, signer =>
|
|
146
|
+
signer.signTransaction(derivationPath, preparedTransaction.hash),
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
observer.next({
|
|
150
|
+
status: PreApprovalStatus.SUBMIT,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const { isApproved } = await submitPreApprovalTransaction(
|
|
154
|
+
partyId,
|
|
155
|
+
preparedTransaction,
|
|
156
|
+
signature,
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
observer.next({
|
|
160
|
+
status: PreApprovalStatus.SUCCESS,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
observer.next({
|
|
164
|
+
isApproved,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// TODO: remove after demo
|
|
168
|
+
const handleTapRequest = async () => {
|
|
169
|
+
try {
|
|
170
|
+
observer.next({
|
|
171
|
+
status: PreApprovalStatus.PREPARE,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const { serialized, hash } = await prepareTapRequest({
|
|
175
|
+
partyId,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
observer.next({
|
|
179
|
+
status: PreApprovalStatus.SIGN,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const signature = await signerContext(deviceId, signer =>
|
|
183
|
+
signer.signTransaction(derivationPath, hash),
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
observer.next({
|
|
187
|
+
status: PreApprovalStatus.SUBMIT,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
await submitTapRequest({
|
|
191
|
+
partyId,
|
|
192
|
+
serialized,
|
|
193
|
+
signature,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
observer.next({
|
|
197
|
+
status: PreApprovalStatus.SUCCESS,
|
|
198
|
+
});
|
|
199
|
+
} catch (err) {
|
|
200
|
+
// Tap request failure should not break the pre-approval flow
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
await handleTapRequest();
|
|
204
|
+
|
|
205
|
+
observer.complete();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
main().then(
|
|
209
|
+
() => observer.complete(),
|
|
210
|
+
error => {
|
|
211
|
+
log("[buildAuthorizePreapproval] Error:", error);
|
|
212
|
+
observer.error(error);
|
|
213
|
+
},
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const log = (message: string, ...rest: any[]) => {
|
|
218
|
+
// eslint-disable-next-line no-console
|
|
219
|
+
console.log(message, ...rest);
|
|
220
|
+
};
|
|
@@ -1,25 +1,16 @@
|
|
|
1
1
|
import { AccountBridge } from "@ledgerhq/types-live";
|
|
2
2
|
import { Transaction } from "../types";
|
|
3
|
-
import {
|
|
4
|
-
import { getNextSequence } from "../network/node";
|
|
3
|
+
import { estimateFees } from "../common-logic";
|
|
5
4
|
import BigNumber from "bignumber.js";
|
|
5
|
+
import { updateTransaction } from "./updateTransaction";
|
|
6
6
|
|
|
7
7
|
export const prepareTransaction: AccountBridge<Transaction>["prepareTransaction"] = async (
|
|
8
8
|
account,
|
|
9
9
|
transaction,
|
|
10
10
|
) => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
{ address: account.freshAddress, nextSequenceNumber: seq },
|
|
15
|
-
{ amount: transaction.amount, recipient: transaction.recipient },
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
const fee = await estimateFees(craftedTransaction.serializedTransaction);
|
|
19
|
-
|
|
20
|
-
if (transaction.fee !== new BigNumber(fee.toString())) {
|
|
21
|
-
return { ...transaction, fee: new BigNumber(fee.toString()) };
|
|
11
|
+
let fee = transaction.fee;
|
|
12
|
+
if (!fee || fee.eq(0)) {
|
|
13
|
+
fee = BigNumber((await estimateFees("")).toString());
|
|
22
14
|
}
|
|
23
|
-
|
|
24
|
-
return transaction;
|
|
15
|
+
return updateTransaction(transaction, { fee });
|
|
25
16
|
};
|
|
@@ -4,7 +4,7 @@ import { AccountBridge, Operation } from "@ledgerhq/types-live";
|
|
|
4
4
|
import { SignerContext } from "@ledgerhq/coin-framework/signer";
|
|
5
5
|
import { encodeOperationId } from "@ledgerhq/coin-framework/operation";
|
|
6
6
|
import { combine, craftTransaction, getNextValidSequence } from "../common-logic";
|
|
7
|
-
import { Transaction, CantonSigner
|
|
7
|
+
import { Transaction, CantonSigner } from "../types";
|
|
8
8
|
|
|
9
9
|
export const buildSignOperation =
|
|
10
10
|
(signerContext: SignerContext<CantonSigner>): AccountBridge<Transaction>["signOperation"] =>
|
|
@@ -34,7 +34,8 @@ export const buildSignOperation =
|
|
|
34
34
|
{
|
|
35
35
|
recipient: transaction.recipient,
|
|
36
36
|
amount: transaction.amount,
|
|
37
|
-
|
|
37
|
+
expireInSeconds: 60 * 60 * 24,
|
|
38
|
+
tokenId: "Amulet",
|
|
38
39
|
},
|
|
39
40
|
);
|
|
40
41
|
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import BigNumber from "bignumber.js";
|
|
2
|
+
import { AccountShapeInfo } from "@ledgerhq/coin-framework/bridge/jsHelpers";
|
|
3
|
+
import {
|
|
4
|
+
getDerivationModesForCurrency,
|
|
5
|
+
getDerivationScheme,
|
|
6
|
+
runDerivationScheme,
|
|
7
|
+
} from "@ledgerhq/coin-framework/derivation";
|
|
8
|
+
import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies";
|
|
9
|
+
import { Account, Operation } from "@ledgerhq/types-live";
|
|
10
|
+
import coinConfig from "../config";
|
|
11
|
+
import * as gateway from "../network/gateway";
|
|
12
|
+
import { getAccountShape } from "./sync";
|
|
13
|
+
|
|
14
|
+
const TEST_ADDRESS =
|
|
15
|
+
"b6400f93ea1c74aea86be39b0ccc846fc5de01f12b2ad0d7c31848d6fb6eb6d9::1220c81315e2bf2524a9141bcc6cbf19b61c151e0dcaa95343c0ccf53aed7415c4ec";
|
|
16
|
+
const currency = getCryptoCurrencyById("canton_network");
|
|
17
|
+
const derivationMode = getDerivationModesForCurrency(currency)[0];
|
|
18
|
+
const derivationPath = runDerivationScheme(
|
|
19
|
+
getDerivationScheme({ derivationMode, currency }),
|
|
20
|
+
currency,
|
|
21
|
+
{
|
|
22
|
+
account: 0,
|
|
23
|
+
},
|
|
24
|
+
);
|
|
25
|
+
const ACCOUNT_SHAPE_INFO: AccountShapeInfo = {
|
|
26
|
+
address: TEST_ADDRESS,
|
|
27
|
+
currency,
|
|
28
|
+
derivationMode,
|
|
29
|
+
derivationPath,
|
|
30
|
+
index: 0,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
describe("sync (devnet)", () => {
|
|
34
|
+
beforeAll(async () => {
|
|
35
|
+
coinConfig.setCoinConfig(() => ({
|
|
36
|
+
gatewayUrl: "https://canton-gateway.api.live.ledger-test.com",
|
|
37
|
+
useGateway: true,
|
|
38
|
+
networkType: "devnet",
|
|
39
|
+
status: {
|
|
40
|
+
type: "active",
|
|
41
|
+
},
|
|
42
|
+
}));
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("getAccountShape", () => {
|
|
46
|
+
it("should fetch account shape for a valid address", async () => {
|
|
47
|
+
const result = await getAccountShape(ACCOUNT_SHAPE_INFO, { paginationConfig: {} });
|
|
48
|
+
|
|
49
|
+
expect(result).toBeDefined();
|
|
50
|
+
expect(result.id).toBeDefined();
|
|
51
|
+
expect(result.xpub).toBe(TEST_ADDRESS.replace(/:/g, "_"));
|
|
52
|
+
expect(result.blockHeight).toBeGreaterThan(0);
|
|
53
|
+
expect(result.balance).toBeDefined();
|
|
54
|
+
expect(result.spendableBalance).toBeDefined();
|
|
55
|
+
expect(result.operations).toBeDefined();
|
|
56
|
+
expect(result.operationsCount).toBeGreaterThanOrEqual(0);
|
|
57
|
+
|
|
58
|
+
expect(result.balance).toBeInstanceOf(Object);
|
|
59
|
+
expect(result.balance?.toNumber).toBeDefined();
|
|
60
|
+
expect(result.spendableBalance).toBeInstanceOf(Object);
|
|
61
|
+
expect(result.spendableBalance?.toNumber).toBeDefined();
|
|
62
|
+
|
|
63
|
+
expect(result.spendableBalance?.toNumber()).toBeLessThanOrEqual(
|
|
64
|
+
result.balance?.toNumber() || 0,
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should handle address with colons correctly", async () => {
|
|
69
|
+
const result = await getAccountShape(ACCOUNT_SHAPE_INFO, { paginationConfig: {} });
|
|
70
|
+
|
|
71
|
+
expect(result.xpub).toBe(TEST_ADDRESS.replace(/:/g, "_"));
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("should merge operations correctly with initial account", async () => {
|
|
75
|
+
const operations: Operation[] = [
|
|
76
|
+
{
|
|
77
|
+
id: "test-op-1",
|
|
78
|
+
hash: "test-hash-1",
|
|
79
|
+
accountId: "test-account",
|
|
80
|
+
type: "OUT" as const,
|
|
81
|
+
value: new BigNumber(1000000),
|
|
82
|
+
fee: new BigNumber(100000),
|
|
83
|
+
blockHash: "block-hash-1",
|
|
84
|
+
blockHeight: 100,
|
|
85
|
+
senders: [TEST_ADDRESS],
|
|
86
|
+
recipients: ["recipient-1"],
|
|
87
|
+
date: new Date("2023-01-01"),
|
|
88
|
+
transactionSequenceNumber: 100,
|
|
89
|
+
extra: { uid: "uid-1" },
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
const result = await getAccountShape(
|
|
94
|
+
{
|
|
95
|
+
...ACCOUNT_SHAPE_INFO,
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
97
|
+
initialAccount: { operations } as Account,
|
|
98
|
+
},
|
|
99
|
+
{ paginationConfig: {} },
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
expect(result.operations).toBeDefined();
|
|
103
|
+
expect(result.operationsCount).toBeGreaterThanOrEqual(1);
|
|
104
|
+
const initialOp = result.operations?.find(op => op.id === "test-op-1");
|
|
105
|
+
expect(initialOp).toBeDefined();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("should take locked balance into account when calculating spendable balance", async () => {
|
|
109
|
+
const mockGetBalance = jest.spyOn(gateway, "getBalance");
|
|
110
|
+
|
|
111
|
+
mockGetBalance.mockResolvedValue([
|
|
112
|
+
{
|
|
113
|
+
instrument_id: "canton_network",
|
|
114
|
+
amount: 1000000,
|
|
115
|
+
locked: true,
|
|
116
|
+
},
|
|
117
|
+
]);
|
|
118
|
+
|
|
119
|
+
const result = await getAccountShape(ACCOUNT_SHAPE_INFO, { paginationConfig: {} });
|
|
120
|
+
|
|
121
|
+
expect(result.balance?.toNumber()).toBe(1000000);
|
|
122
|
+
expect(result.spendableBalance?.toNumber()).toBe(0);
|
|
123
|
+
|
|
124
|
+
mockGetBalance.mockRestore();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("should call getOperations with correct cursor based with initial account", async () => {
|
|
128
|
+
const mockGetOperations = jest.spyOn(gateway, "getOperations");
|
|
129
|
+
const operation: Operation = {
|
|
130
|
+
id: "test-op-1",
|
|
131
|
+
hash: "test-hash-1",
|
|
132
|
+
accountId: "test-account",
|
|
133
|
+
type: "OUT" as const,
|
|
134
|
+
value: new BigNumber(1000000),
|
|
135
|
+
fee: new BigNumber(100000),
|
|
136
|
+
blockHash: "block-hash-1",
|
|
137
|
+
blockHeight: 100,
|
|
138
|
+
senders: [TEST_ADDRESS],
|
|
139
|
+
recipients: ["recipient-1"],
|
|
140
|
+
date: new Date("2023-01-01"),
|
|
141
|
+
transactionSequenceNumber: 100,
|
|
142
|
+
extra: { uid: "uid-1" },
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
146
|
+
const initialAccount = { operations: [operation] } as Account;
|
|
147
|
+
|
|
148
|
+
const result = await getAccountShape(
|
|
149
|
+
{
|
|
150
|
+
...ACCOUNT_SHAPE_INFO,
|
|
151
|
+
initialAccount,
|
|
152
|
+
},
|
|
153
|
+
{ paginationConfig: {} },
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
expect(mockGetOperations).toHaveBeenCalledWith(TEST_ADDRESS, {
|
|
157
|
+
cursor: (operation.blockHeight || 0) + 1,
|
|
158
|
+
limit: 100,
|
|
159
|
+
});
|
|
160
|
+
expect(result.operations).toBeDefined();
|
|
161
|
+
expect(result.operationsCount).toBeGreaterThan(1);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("should call getOperations with cursor 0 when no initial account", async () => {
|
|
165
|
+
const mockGetOperations = jest.spyOn(gateway, "getOperations");
|
|
166
|
+
|
|
167
|
+
const result = await getAccountShape(ACCOUNT_SHAPE_INFO, { paginationConfig: {} });
|
|
168
|
+
|
|
169
|
+
expect(result.operations).toBeDefined();
|
|
170
|
+
expect(result.operationsCount).toBeGreaterThan(1);
|
|
171
|
+
|
|
172
|
+
expect(mockGetOperations).toHaveBeenCalledWith(TEST_ADDRESS, {
|
|
173
|
+
cursor: 0,
|
|
174
|
+
limit: 100,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
mockGetOperations.mockRestore();
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
});
|
package/src/bridge/sync.ts
CHANGED
|
@@ -2,101 +2,109 @@ import BigNumber from "bignumber.js";
|
|
|
2
2
|
import { Operation } from "@ledgerhq/types-live";
|
|
3
3
|
import { encodeAccountId } from "@ledgerhq/coin-framework/account/index";
|
|
4
4
|
import { GetAccountShape, mergeOps } from "@ledgerhq/coin-framework/bridge/jsHelpers";
|
|
5
|
-
import {
|
|
6
|
-
import { getAccountInfo, getLedgerEnd } from "../network/node";
|
|
5
|
+
import { getBalance, getLedgerEnd, getOperations, type OperationInfo } from "../network/gateway";
|
|
7
6
|
|
|
8
7
|
import { encodeOperationId } from "@ledgerhq/coin-framework/operation";
|
|
9
|
-
import { BoilerplateOperation } from "../network/types";
|
|
10
8
|
import coinConfig from "../config";
|
|
11
9
|
|
|
12
|
-
const
|
|
10
|
+
const txInfoToOperationAdapter =
|
|
13
11
|
(accountId: string, address: string) =>
|
|
14
|
-
({
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
12
|
+
(txInfo: OperationInfo): Operation => {
|
|
13
|
+
const {
|
|
14
|
+
transaction_hash,
|
|
15
|
+
uid,
|
|
16
|
+
block: { height, hash },
|
|
17
|
+
senders,
|
|
18
|
+
recipients,
|
|
19
|
+
transaction_timestamp,
|
|
20
|
+
fee: { value: fee },
|
|
21
|
+
transfers: [{ value: transferValue }],
|
|
22
|
+
} = txInfo;
|
|
23
|
+
|
|
24
|
+
const type = senders.includes(address) ? "OUT" : "IN";
|
|
25
|
+
const value = new BigNumber(transferValue);
|
|
26
|
+
const feeValue = new BigNumber(fee);
|
|
30
27
|
|
|
31
28
|
const op: Operation = {
|
|
32
|
-
id: encodeOperationId(accountId,
|
|
33
|
-
hash:
|
|
29
|
+
id: encodeOperationId(accountId, transaction_hash, type),
|
|
30
|
+
hash: transaction_hash,
|
|
34
31
|
accountId,
|
|
35
32
|
type,
|
|
36
33
|
value,
|
|
37
34
|
fee: feeValue,
|
|
38
|
-
blockHash:
|
|
39
|
-
blockHeight:
|
|
40
|
-
senders
|
|
41
|
-
recipients
|
|
42
|
-
date: new Date(),
|
|
43
|
-
transactionSequenceNumber:
|
|
44
|
-
extra: {
|
|
35
|
+
blockHash: hash,
|
|
36
|
+
blockHeight: height,
|
|
37
|
+
senders,
|
|
38
|
+
recipients,
|
|
39
|
+
date: new Date(transaction_timestamp),
|
|
40
|
+
transactionSequenceNumber: height,
|
|
41
|
+
extra: {
|
|
42
|
+
uid,
|
|
43
|
+
},
|
|
45
44
|
};
|
|
46
45
|
|
|
47
46
|
return op;
|
|
48
47
|
};
|
|
49
48
|
|
|
50
49
|
const filterOperations = (
|
|
51
|
-
transactions:
|
|
50
|
+
transactions: OperationInfo[],
|
|
52
51
|
accountId: string,
|
|
53
52
|
address: string,
|
|
54
|
-
) => {
|
|
53
|
+
): Operation[] => {
|
|
55
54
|
return transactions
|
|
56
|
-
.filter(
|
|
57
|
-
|
|
58
|
-
tx.TransactionType === "Payment" && typeof meta.delivered_amount === "string",
|
|
59
|
-
)
|
|
60
|
-
.map(operationAdapter(accountId, address))
|
|
61
|
-
.filter((op): op is Operation => Boolean(op));
|
|
55
|
+
.filter(tx => tx.type === "Receive" || tx.type === "Send")
|
|
56
|
+
.map(txInfoToOperationAdapter(accountId, address));
|
|
62
57
|
};
|
|
63
58
|
|
|
64
59
|
export const getAccountShape: GetAccountShape = async info => {
|
|
65
60
|
const { address, initialAccount, currency, derivationMode } = info;
|
|
61
|
+
// TODO: we need better solution ?
|
|
62
|
+
const xpubOrAddress = address?.replace(/:/g, "_") || "";
|
|
66
63
|
|
|
67
64
|
const accountId = encodeAccountId({
|
|
68
65
|
type: "js",
|
|
69
66
|
version: "2",
|
|
70
67
|
currencyId: currency.id,
|
|
71
|
-
xpubOrAddress
|
|
72
|
-
derivationMode,
|
|
68
|
+
xpubOrAddress,
|
|
69
|
+
derivationMode: "",
|
|
73
70
|
});
|
|
74
71
|
|
|
75
72
|
// blockheight retrieval
|
|
76
73
|
const blockHeight = await getLedgerEnd();
|
|
77
74
|
|
|
78
75
|
// Account info retrieval + spendable balance calculation
|
|
79
|
-
const accountInfo = await getAccountInfo(address);
|
|
80
|
-
const
|
|
76
|
+
// const accountInfo = await getAccountInfo(address);
|
|
77
|
+
const balances = await getBalance(address);
|
|
78
|
+
|
|
79
|
+
const balanceData = balances.find(balance => balance.instrument_id === "canton_network") || {
|
|
80
|
+
instrument_id: "canton_network",
|
|
81
|
+
amount: 0,
|
|
82
|
+
locked: false,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const balance = new BigNumber(balanceData.amount);
|
|
81
86
|
const reserveMin = coinConfig.getCoinConfig().minReserve || 0;
|
|
82
|
-
const
|
|
83
|
-
|
|
87
|
+
const lockedAmount = balanceData.locked ? balance : new BigNumber(0);
|
|
88
|
+
const spendableBalance = BigNumber.max(
|
|
89
|
+
0,
|
|
90
|
+
balance.minus(lockedAmount).minus(BigNumber(reserveMin)),
|
|
84
91
|
);
|
|
85
92
|
|
|
86
93
|
// Tx history fetching
|
|
87
94
|
const oldOperations = initialAccount?.operations || [];
|
|
88
95
|
const startAt = oldOperations.length ? (oldOperations[0].blockHeight || 0) + 1 : 0;
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
96
|
+
const transactionData = await getOperations(address, {
|
|
97
|
+
cursor: 0,
|
|
98
|
+
limit: 100,
|
|
92
99
|
});
|
|
93
|
-
|
|
94
|
-
const
|
|
100
|
+
|
|
101
|
+
const newOperations = filterOperations(transactionData.operations, accountId, address);
|
|
102
|
+
const operations = mergeOps(oldOperations, newOperations);
|
|
95
103
|
|
|
96
104
|
// We return the new account shape
|
|
97
105
|
const shape = {
|
|
98
106
|
id: accountId,
|
|
99
|
-
xpub:
|
|
107
|
+
xpub: xpubOrAddress,
|
|
100
108
|
blockHeight,
|
|
101
109
|
balance,
|
|
102
110
|
spendableBalance,
|
|
@@ -8,10 +8,5 @@ import type { Transaction } from "../types";
|
|
|
8
8
|
// NOTE: here is an example transaction updater function
|
|
9
9
|
// in this case, it resets fee to null depending on the patch content
|
|
10
10
|
export const updateTransaction: AccountBridge<Transaction>["updateTransaction"] = (tx, patch) => {
|
|
11
|
-
// eslint-disable-next-line no-constant-condition
|
|
12
|
-
if (patch.recipient === "boilerplate1" || true) {
|
|
13
|
-
patch = { ...patch, fee: null };
|
|
14
|
-
}
|
|
15
|
-
|
|
16
11
|
return defaultUpdateTransaction(tx, patch);
|
|
17
12
|
};
|