@yellow-org/sdk 1.0.1-alpha.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/README.md +999 -0
- package/dist/abis/generated.d.ts +4300 -0
- package/dist/abis/generated.js +1 -0
- package/dist/app/index.d.ts +2 -0
- package/dist/app/index.js +2 -0
- package/dist/app/packing.d.ts +7 -0
- package/dist/app/packing.js +111 -0
- package/dist/app/types.d.ts +91 -0
- package/dist/app/types.js +42 -0
- package/dist/asset_store.d.ts +13 -0
- package/dist/asset_store.js +121 -0
- package/dist/blockchain/evm/channel_hub_abi.d.ts +4284 -0
- package/dist/blockchain/evm/channel_hub_abi.js +5475 -0
- package/dist/blockchain/evm/client.d.ts +46 -0
- package/dist/blockchain/evm/client.js +428 -0
- package/dist/blockchain/evm/erc20.d.ts +13 -0
- package/dist/blockchain/evm/erc20.js +54 -0
- package/dist/blockchain/evm/erc20_abi.d.ts +144 -0
- package/dist/blockchain/evm/erc20_abi.js +96 -0
- package/dist/blockchain/evm/index.d.ts +6 -0
- package/dist/blockchain/evm/index.js +6 -0
- package/dist/blockchain/evm/interface.d.ts +10 -0
- package/dist/blockchain/evm/interface.js +1 -0
- package/dist/blockchain/evm/types.d.ts +27 -0
- package/dist/blockchain/evm/types.js +1 -0
- package/dist/blockchain/evm/utils.d.ts +9 -0
- package/dist/blockchain/evm/utils.js +129 -0
- package/dist/blockchain/index.d.ts +1 -0
- package/dist/blockchain/index.js +1 -0
- package/dist/client.d.ts +99 -0
- package/dist/client.js +720 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.js +28 -0
- package/dist/core/event.d.ts +29 -0
- package/dist/core/event.js +1 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.js +6 -0
- package/dist/core/interface.d.ts +53 -0
- package/dist/core/interface.js +1 -0
- package/dist/core/state.d.ts +21 -0
- package/dist/core/state.js +267 -0
- package/dist/core/state_packer.d.ts +13 -0
- package/dist/core/state_packer.js +98 -0
- package/dist/core/types.d.ts +203 -0
- package/dist/core/types.js +220 -0
- package/dist/core/utils.d.ts +16 -0
- package/dist/core/utils.js +181 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/rpc/api.d.ts +181 -0
- package/dist/rpc/api.js +1 -0
- package/dist/rpc/client.d.ts +36 -0
- package/dist/rpc/client.js +102 -0
- package/dist/rpc/dialer.d.ts +30 -0
- package/dist/rpc/dialer.js +146 -0
- package/dist/rpc/error.d.ts +18 -0
- package/dist/rpc/error.js +27 -0
- package/dist/rpc/index.d.ts +7 -0
- package/dist/rpc/index.js +7 -0
- package/dist/rpc/message.d.ts +25 -0
- package/dist/rpc/message.js +102 -0
- package/dist/rpc/methods.d.ts +30 -0
- package/dist/rpc/methods.js +27 -0
- package/dist/rpc/types.d.ts +101 -0
- package/dist/rpc/types.js +1 -0
- package/dist/signers.d.ts +48 -0
- package/dist/signers.js +96 -0
- package/dist/utils/sign.d.ts +2 -0
- package/dist/utils/sign.js +8 -0
- package/dist/utils.d.ts +42 -0
- package/dist/utils.js +226 -0
- package/package.json +89 -0
package/dist/client.js
ADDED
|
@@ -0,0 +1,720 @@
|
|
|
1
|
+
import { createPublicClient, createWalletClient, http, custom, verifyMessage } from 'viem';
|
|
2
|
+
import * as core from './core';
|
|
3
|
+
import * as app from './app';
|
|
4
|
+
import { RPCClient } from './rpc/client';
|
|
5
|
+
import { WebsocketDialer } from './rpc/dialer';
|
|
6
|
+
import { ClientAssetStore } from './asset_store';
|
|
7
|
+
import { DefaultConfig } from './config';
|
|
8
|
+
import { generateNonce, transformNodeConfig, transformAssets, transformBalances, transformChannel, transformState, transformTransaction, transformPaginationMetadata, transformAppDefinitionToRPC, transformAppStateUpdateToRPC, transformSignedAppStateUpdateToRPC, transformAppSessionInfo, transformAppDefinitionFromRPC, } from './utils';
|
|
9
|
+
import * as blockchain from './blockchain';
|
|
10
|
+
import { nextState, applyChannelCreation, applyAcknowledgementTransition, applyHomeDepositTransition, applyHomeWithdrawalTransition, applyTransferSendTransition, applyFinalizeTransition, applyCommitTransition } from './core/state';
|
|
11
|
+
import { newVoidState } from './core/types';
|
|
12
|
+
import { packState, packChallengeState } from './core/state_packer';
|
|
13
|
+
export const DEFAULT_CHALLENGE_PERIOD = 86400;
|
|
14
|
+
function stripSignerTypePrefix(sig) {
|
|
15
|
+
if (sig.length < 6) {
|
|
16
|
+
throw new Error(`signature too short to contain a signer type prefix: ${sig}`);
|
|
17
|
+
}
|
|
18
|
+
const prefixByte = parseInt(sig.slice(2, 4), 16);
|
|
19
|
+
if (prefixByte !== core.ChannelSignerType.Default) {
|
|
20
|
+
throw new Error(`expected ChannelDefaultSigner prefix 0x00, got 0x${prefixByte.toString(16).padStart(2, '0')}; ` +
|
|
21
|
+
`session key signing requires the default wallet signer, not a session key signer`);
|
|
22
|
+
}
|
|
23
|
+
return `0x${sig.slice(4)}`;
|
|
24
|
+
}
|
|
25
|
+
export class Client {
|
|
26
|
+
constructor(rpcClient, config, stateSigner, txSigner, assetStore) {
|
|
27
|
+
this.rpcClient = rpcClient;
|
|
28
|
+
this.config = config;
|
|
29
|
+
this.stateSigner = stateSigner;
|
|
30
|
+
this.txSigner = txSigner;
|
|
31
|
+
this.assetStore = assetStore;
|
|
32
|
+
this.blockchainClients = new Map();
|
|
33
|
+
this.homeBlockchains = new Map();
|
|
34
|
+
this.exitPromise = new Promise((resolve) => {
|
|
35
|
+
this.exitResolve = resolve;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
static async create(wsURL, stateSigner, txSigner, ...opts) {
|
|
39
|
+
const config = {
|
|
40
|
+
url: wsURL,
|
|
41
|
+
handshakeTimeout: DefaultConfig.handshakeTimeout,
|
|
42
|
+
pingInterval: DefaultConfig.pingInterval,
|
|
43
|
+
errorHandler: DefaultConfig.errorHandler,
|
|
44
|
+
blockchainRPCs: DefaultConfig.blockchainRPCs || new Map(),
|
|
45
|
+
};
|
|
46
|
+
for (const opt of opts) {
|
|
47
|
+
opt(config);
|
|
48
|
+
}
|
|
49
|
+
const dialer = new WebsocketDialer();
|
|
50
|
+
const rpcClient = new RPCClient(dialer);
|
|
51
|
+
let client;
|
|
52
|
+
const assetStore = new ClientAssetStore(async () => {
|
|
53
|
+
return await client.getAssets();
|
|
54
|
+
});
|
|
55
|
+
client = new Client(rpcClient, config, stateSigner, txSigner, assetStore);
|
|
56
|
+
const handleError = (err) => {
|
|
57
|
+
if (err && config.errorHandler) {
|
|
58
|
+
config.errorHandler(err);
|
|
59
|
+
}
|
|
60
|
+
client.exitResolve?.();
|
|
61
|
+
};
|
|
62
|
+
await rpcClient.start(wsURL, handleError);
|
|
63
|
+
return client;
|
|
64
|
+
}
|
|
65
|
+
async setHomeBlockchain(asset, blockchainId) {
|
|
66
|
+
const existingBlockchainId = this.homeBlockchains.get(asset);
|
|
67
|
+
if (existingBlockchainId !== undefined) {
|
|
68
|
+
throw new Error(`home blockchain is already set for asset ${asset} to ${existingBlockchainId}, please use Migrate() if you want to change home blockchain`);
|
|
69
|
+
}
|
|
70
|
+
const exists = await this.assetStore.assetExistsOnBlockchain(blockchainId, asset);
|
|
71
|
+
if (!exists) {
|
|
72
|
+
throw new Error(`asset ${asset} not supported on blockchain ${blockchainId}`);
|
|
73
|
+
}
|
|
74
|
+
this.homeBlockchains.set(asset, blockchainId);
|
|
75
|
+
}
|
|
76
|
+
async close() {
|
|
77
|
+
this.exitResolve?.();
|
|
78
|
+
}
|
|
79
|
+
waitForClose() {
|
|
80
|
+
return this.exitPromise;
|
|
81
|
+
}
|
|
82
|
+
async signState(state) {
|
|
83
|
+
const packed = await packState(state, this.assetStore);
|
|
84
|
+
const signature = await this.stateSigner.signMessage(packed);
|
|
85
|
+
return signature;
|
|
86
|
+
}
|
|
87
|
+
getUserAddress() {
|
|
88
|
+
return this.stateSigner.getAddress();
|
|
89
|
+
}
|
|
90
|
+
async signAndSubmitState(state) {
|
|
91
|
+
const sig = await this.signState(state);
|
|
92
|
+
state.userSig = sig;
|
|
93
|
+
const nodeSig = await this.submitState(state);
|
|
94
|
+
state.nodeSig = nodeSig;
|
|
95
|
+
return nodeSig;
|
|
96
|
+
}
|
|
97
|
+
async deposit(blockchainId, asset, amount) {
|
|
98
|
+
const userWallet = this.getUserAddress();
|
|
99
|
+
const nodeAddress = await this.getNodeAddress();
|
|
100
|
+
if (!nodeAddress) {
|
|
101
|
+
throw new Error('node address is undefined - ensure node config is properly loaded');
|
|
102
|
+
}
|
|
103
|
+
const tokenAddress = await this.assetStore.getTokenAddress(asset, blockchainId);
|
|
104
|
+
if (!tokenAddress) {
|
|
105
|
+
throw new Error(`token address not found for asset ${asset} on blockchain ${blockchainId}`);
|
|
106
|
+
}
|
|
107
|
+
let state = null;
|
|
108
|
+
let channelIsOpen = false;
|
|
109
|
+
try {
|
|
110
|
+
state = await this.getLatestState(userWallet, asset, false);
|
|
111
|
+
if (state && state.homeChannelId) {
|
|
112
|
+
const hasFinalize = state.transition.type === core.TransitionType.Finalize;
|
|
113
|
+
channelIsOpen = !hasFinalize;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
}
|
|
118
|
+
if (!state || !state.homeChannelId || !channelIsOpen) {
|
|
119
|
+
const bitmap = await this.getSupportedSigValidatorsBitmap();
|
|
120
|
+
const channelDef = {
|
|
121
|
+
nonce: generateNonce(),
|
|
122
|
+
challenge: DEFAULT_CHALLENGE_PERIOD,
|
|
123
|
+
approvedSigValidators: bitmap,
|
|
124
|
+
};
|
|
125
|
+
if (!state) {
|
|
126
|
+
state = newVoidState(asset, userWallet);
|
|
127
|
+
}
|
|
128
|
+
const newState = nextState(state);
|
|
129
|
+
applyChannelCreation(newState, channelDef, blockchainId, tokenAddress, nodeAddress);
|
|
130
|
+
applyHomeDepositTransition(newState, amount);
|
|
131
|
+
const sig = await this.signState(newState);
|
|
132
|
+
newState.userSig = sig;
|
|
133
|
+
const nodeSig = await this.requestChannelCreation(newState, channelDef);
|
|
134
|
+
newState.nodeSig = nodeSig;
|
|
135
|
+
return newState;
|
|
136
|
+
}
|
|
137
|
+
const newState = nextState(state);
|
|
138
|
+
applyHomeDepositTransition(newState, amount);
|
|
139
|
+
await this.signAndSubmitState(newState);
|
|
140
|
+
return newState;
|
|
141
|
+
}
|
|
142
|
+
async withdraw(blockchainId, asset, amount) {
|
|
143
|
+
const userWallet = this.getUserAddress();
|
|
144
|
+
const nodeAddress = await this.getNodeAddress();
|
|
145
|
+
if (!nodeAddress) {
|
|
146
|
+
throw new Error('node address is undefined - ensure node config is properly loaded');
|
|
147
|
+
}
|
|
148
|
+
const tokenAddress = await this.assetStore.getTokenAddress(asset, blockchainId);
|
|
149
|
+
if (!tokenAddress) {
|
|
150
|
+
throw new Error(`token address not found for asset ${asset} on blockchain ${blockchainId}`);
|
|
151
|
+
}
|
|
152
|
+
let state = null;
|
|
153
|
+
let channelIsOpen = false;
|
|
154
|
+
try {
|
|
155
|
+
state = await this.getLatestState(userWallet, asset, false);
|
|
156
|
+
if (state && state.homeChannelId) {
|
|
157
|
+
const hasFinalize = state.transition.type === core.TransitionType.Finalize;
|
|
158
|
+
channelIsOpen = !hasFinalize;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
}
|
|
163
|
+
if (!state || !state.homeChannelId || !channelIsOpen) {
|
|
164
|
+
const bitmap = await this.getSupportedSigValidatorsBitmap();
|
|
165
|
+
const channelDef = {
|
|
166
|
+
nonce: generateNonce(),
|
|
167
|
+
challenge: DEFAULT_CHALLENGE_PERIOD,
|
|
168
|
+
approvedSigValidators: bitmap,
|
|
169
|
+
};
|
|
170
|
+
if (!state) {
|
|
171
|
+
state = newVoidState(asset, userWallet);
|
|
172
|
+
}
|
|
173
|
+
const newState = nextState(state);
|
|
174
|
+
applyChannelCreation(newState, channelDef, blockchainId, tokenAddress, nodeAddress);
|
|
175
|
+
applyHomeWithdrawalTransition(newState, amount);
|
|
176
|
+
const sig = await this.signState(newState);
|
|
177
|
+
newState.userSig = sig;
|
|
178
|
+
const nodeSig = await this.requestChannelCreation(newState, channelDef);
|
|
179
|
+
newState.nodeSig = nodeSig;
|
|
180
|
+
return newState;
|
|
181
|
+
}
|
|
182
|
+
const newState = nextState(state);
|
|
183
|
+
applyHomeWithdrawalTransition(newState, amount);
|
|
184
|
+
await this.signAndSubmitState(newState);
|
|
185
|
+
return newState;
|
|
186
|
+
}
|
|
187
|
+
async transfer(recipientWallet, asset, amount) {
|
|
188
|
+
const senderWallet = this.getUserAddress();
|
|
189
|
+
let state = null;
|
|
190
|
+
try {
|
|
191
|
+
state = await this.getLatestState(senderWallet, asset, false);
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
}
|
|
195
|
+
if (!state || !state.homeChannelId) {
|
|
196
|
+
const bitmap = await this.getSupportedSigValidatorsBitmap();
|
|
197
|
+
const channelDef = {
|
|
198
|
+
nonce: generateNonce(),
|
|
199
|
+
challenge: DEFAULT_CHALLENGE_PERIOD,
|
|
200
|
+
approvedSigValidators: bitmap,
|
|
201
|
+
};
|
|
202
|
+
if (!state) {
|
|
203
|
+
state = newVoidState(asset, senderWallet);
|
|
204
|
+
}
|
|
205
|
+
const newState = nextState(state);
|
|
206
|
+
let blockchainId = this.homeBlockchains.get(asset);
|
|
207
|
+
if (!blockchainId) {
|
|
208
|
+
if (state.homeLedger.blockchainId !== 0n) {
|
|
209
|
+
blockchainId = state.homeLedger.blockchainId;
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
blockchainId = await this.assetStore.getSuggestedBlockchainId(asset);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const nodeAddress = await this.getNodeAddress();
|
|
216
|
+
if (!nodeAddress) {
|
|
217
|
+
throw new Error('node address is undefined - ensure node config is properly loaded');
|
|
218
|
+
}
|
|
219
|
+
const tokenAddress = await this.assetStore.getTokenAddress(asset, blockchainId);
|
|
220
|
+
if (!tokenAddress) {
|
|
221
|
+
throw new Error(`token address not found for asset ${asset} on blockchain ${blockchainId}`);
|
|
222
|
+
}
|
|
223
|
+
applyChannelCreation(newState, channelDef, blockchainId, tokenAddress, nodeAddress);
|
|
224
|
+
applyTransferSendTransition(newState, recipientWallet, amount);
|
|
225
|
+
const sig = await this.signState(newState);
|
|
226
|
+
newState.userSig = sig;
|
|
227
|
+
const nodeSig = await this.requestChannelCreation(newState, channelDef);
|
|
228
|
+
newState.nodeSig = nodeSig;
|
|
229
|
+
return newState;
|
|
230
|
+
}
|
|
231
|
+
const newState = nextState(state);
|
|
232
|
+
applyTransferSendTransition(newState, recipientWallet, amount);
|
|
233
|
+
await this.signAndSubmitState(newState);
|
|
234
|
+
return newState;
|
|
235
|
+
}
|
|
236
|
+
async acknowledge(asset) {
|
|
237
|
+
const userWallet = this.getUserAddress();
|
|
238
|
+
let state = null;
|
|
239
|
+
try {
|
|
240
|
+
state = await this.getLatestState(userWallet, asset, false);
|
|
241
|
+
}
|
|
242
|
+
catch (err) {
|
|
243
|
+
}
|
|
244
|
+
if (!state || !state.homeChannelId) {
|
|
245
|
+
const bitmap = await this.getSupportedSigValidatorsBitmap();
|
|
246
|
+
const channelDef = {
|
|
247
|
+
nonce: generateNonce(),
|
|
248
|
+
challenge: DEFAULT_CHALLENGE_PERIOD,
|
|
249
|
+
approvedSigValidators: bitmap,
|
|
250
|
+
};
|
|
251
|
+
if (!state) {
|
|
252
|
+
state = newVoidState(asset, userWallet);
|
|
253
|
+
}
|
|
254
|
+
const newState = nextState(state);
|
|
255
|
+
let blockchainId = this.homeBlockchains.get(asset);
|
|
256
|
+
if (!blockchainId) {
|
|
257
|
+
if (state.homeLedger.blockchainId !== 0n) {
|
|
258
|
+
blockchainId = state.homeLedger.blockchainId;
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
blockchainId = await this.assetStore.getSuggestedBlockchainId(asset);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const nodeAddress = await this.getNodeAddress();
|
|
265
|
+
if (!nodeAddress) {
|
|
266
|
+
throw new Error('node address is undefined - ensure node config is properly loaded');
|
|
267
|
+
}
|
|
268
|
+
const tokenAddress = await this.assetStore.getTokenAddress(asset, blockchainId);
|
|
269
|
+
if (!tokenAddress) {
|
|
270
|
+
throw new Error(`token address not found for asset ${asset} on blockchain ${blockchainId}`);
|
|
271
|
+
}
|
|
272
|
+
applyChannelCreation(newState, channelDef, blockchainId, tokenAddress, nodeAddress);
|
|
273
|
+
applyAcknowledgementTransition(newState);
|
|
274
|
+
const sig = await this.signState(newState);
|
|
275
|
+
newState.userSig = sig;
|
|
276
|
+
const nodeSig = await this.requestChannelCreation(newState, channelDef);
|
|
277
|
+
newState.nodeSig = nodeSig;
|
|
278
|
+
return newState;
|
|
279
|
+
}
|
|
280
|
+
if (state.userSig) {
|
|
281
|
+
throw new Error('state already acknowledged by user');
|
|
282
|
+
}
|
|
283
|
+
const newState = nextState(state);
|
|
284
|
+
applyAcknowledgementTransition(newState);
|
|
285
|
+
await this.signAndSubmitState(newState);
|
|
286
|
+
return newState;
|
|
287
|
+
}
|
|
288
|
+
async closeHomeChannel(asset) {
|
|
289
|
+
const senderWallet = this.getUserAddress();
|
|
290
|
+
const state = await this.getLatestState(senderWallet, asset, false);
|
|
291
|
+
if (!state.homeChannelId) {
|
|
292
|
+
throw new Error(`no channel exists for asset ${asset}`);
|
|
293
|
+
}
|
|
294
|
+
const newState = nextState(state);
|
|
295
|
+
applyFinalizeTransition(newState);
|
|
296
|
+
await this.signAndSubmitState(newState);
|
|
297
|
+
return newState;
|
|
298
|
+
}
|
|
299
|
+
async checkpoint(asset) {
|
|
300
|
+
const userWallet = this.getUserAddress();
|
|
301
|
+
const state = await this.getLatestState(userWallet, asset, true);
|
|
302
|
+
if (!state.homeChannelId) {
|
|
303
|
+
throw new Error(`no channel exists for asset ${asset}`);
|
|
304
|
+
}
|
|
305
|
+
const blockchainId = state.homeLedger.blockchainId;
|
|
306
|
+
await this.initializeBlockchainClient(blockchainId);
|
|
307
|
+
const blockchainClient = this.blockchainClients.get(blockchainId);
|
|
308
|
+
const channel = await this.getHomeChannel(userWallet, asset);
|
|
309
|
+
switch (state.transition.type) {
|
|
310
|
+
case core.TransitionType.Acknowledgement:
|
|
311
|
+
case core.TransitionType.HomeDeposit:
|
|
312
|
+
case core.TransitionType.HomeWithdrawal:
|
|
313
|
+
case core.TransitionType.TransferSend:
|
|
314
|
+
case core.TransitionType.TransferReceive:
|
|
315
|
+
case core.TransitionType.Commit:
|
|
316
|
+
case core.TransitionType.Release:
|
|
317
|
+
{
|
|
318
|
+
if (channel.status === core.ChannelStatus.Void) {
|
|
319
|
+
const channelDef = {
|
|
320
|
+
nonce: channel.nonce,
|
|
321
|
+
challenge: channel.challengeDuration,
|
|
322
|
+
approvedSigValidators: channel.approvedSigValidators,
|
|
323
|
+
};
|
|
324
|
+
return await blockchainClient.create(channelDef, state);
|
|
325
|
+
}
|
|
326
|
+
return await blockchainClient.checkpoint(state);
|
|
327
|
+
}
|
|
328
|
+
case core.TransitionType.Finalize: {
|
|
329
|
+
return await blockchainClient.close(state);
|
|
330
|
+
}
|
|
331
|
+
default:
|
|
332
|
+
throw new Error(`transition type ${state.transition.type} does not require a blockchain operation`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
async challenge(state) {
|
|
336
|
+
if (!state.userSig || !state.nodeSig) {
|
|
337
|
+
throw new Error('state must have both user and node signatures');
|
|
338
|
+
}
|
|
339
|
+
if (!state.homeChannelId) {
|
|
340
|
+
throw new Error('state must have a home channel ID');
|
|
341
|
+
}
|
|
342
|
+
const packedState = await packState(state, this.assetStore);
|
|
343
|
+
const userSigRaw = `0x${state.userSig.slice(4)}`;
|
|
344
|
+
const userValid = await verifyMessage({
|
|
345
|
+
address: state.userWallet,
|
|
346
|
+
message: { raw: packedState },
|
|
347
|
+
signature: userSigRaw,
|
|
348
|
+
});
|
|
349
|
+
if (!userValid) {
|
|
350
|
+
throw new Error('invalid user signature');
|
|
351
|
+
}
|
|
352
|
+
const nodeAddress = await this.getNodeAddress();
|
|
353
|
+
const nodeSigRaw = `0x${state.nodeSig.slice(4)}`;
|
|
354
|
+
const nodeValid = await verifyMessage({
|
|
355
|
+
address: nodeAddress,
|
|
356
|
+
message: { raw: packedState },
|
|
357
|
+
signature: nodeSigRaw,
|
|
358
|
+
});
|
|
359
|
+
if (!nodeValid) {
|
|
360
|
+
throw new Error('invalid node signature');
|
|
361
|
+
}
|
|
362
|
+
const challengeData = await packChallengeState(state, this.assetStore);
|
|
363
|
+
const challengerSig = await this.stateSigner.signMessage(challengeData);
|
|
364
|
+
const blockchainId = state.homeLedger.blockchainId;
|
|
365
|
+
await this.initializeBlockchainClient(blockchainId);
|
|
366
|
+
const blockchainClient = this.blockchainClients.get(blockchainId);
|
|
367
|
+
return await blockchainClient.challenge(state, challengerSig);
|
|
368
|
+
}
|
|
369
|
+
async approveToken(chainId, asset, amount) {
|
|
370
|
+
await this.initializeBlockchainClient(chainId);
|
|
371
|
+
const blockchainClient = this.blockchainClients.get(chainId);
|
|
372
|
+
return await blockchainClient.approve(asset, amount);
|
|
373
|
+
}
|
|
374
|
+
async checkTokenAllowance(chainId, tokenAddress, owner) {
|
|
375
|
+
await this.initializeBlockchainClient(chainId);
|
|
376
|
+
const blockchainClient = this.blockchainClients.get(chainId);
|
|
377
|
+
return await blockchainClient.checkAllowanceByAddress(tokenAddress, owner);
|
|
378
|
+
}
|
|
379
|
+
async ping() {
|
|
380
|
+
await this.rpcClient.nodeV1Ping();
|
|
381
|
+
}
|
|
382
|
+
async getConfig() {
|
|
383
|
+
const resp = await this.rpcClient.nodeV1GetConfig();
|
|
384
|
+
return transformNodeConfig(resp);
|
|
385
|
+
}
|
|
386
|
+
async getBlockchains() {
|
|
387
|
+
const config = await this.getConfig();
|
|
388
|
+
return config.blockchains;
|
|
389
|
+
}
|
|
390
|
+
async getAssets(blockchainId) {
|
|
391
|
+
const req = {};
|
|
392
|
+
if (blockchainId !== undefined) {
|
|
393
|
+
req.blockchain_id = blockchainId;
|
|
394
|
+
}
|
|
395
|
+
const resp = await this.rpcClient.nodeV1GetAssets(req);
|
|
396
|
+
return transformAssets(resp.assets);
|
|
397
|
+
}
|
|
398
|
+
async getBalances(wallet) {
|
|
399
|
+
const req = {
|
|
400
|
+
wallet,
|
|
401
|
+
};
|
|
402
|
+
const resp = await this.rpcClient.userV1GetBalances(req);
|
|
403
|
+
return transformBalances(resp.balances);
|
|
404
|
+
}
|
|
405
|
+
async getTransactions(wallet, options) {
|
|
406
|
+
const req = {
|
|
407
|
+
wallet,
|
|
408
|
+
asset: options?.asset,
|
|
409
|
+
tx_type: options?.txType,
|
|
410
|
+
from_time: options?.fromTime,
|
|
411
|
+
to_time: options?.toTime,
|
|
412
|
+
pagination: options?.page && options?.pageSize ? {
|
|
413
|
+
offset: (options.page - 1) * options.pageSize,
|
|
414
|
+
limit: options.pageSize,
|
|
415
|
+
} : undefined,
|
|
416
|
+
};
|
|
417
|
+
const resp = await this.rpcClient.userV1GetTransactions(req);
|
|
418
|
+
return {
|
|
419
|
+
transactions: resp.transactions.map(transformTransaction),
|
|
420
|
+
metadata: transformPaginationMetadata(resp.metadata),
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
async getChannels(wallet, options) {
|
|
424
|
+
const req = {
|
|
425
|
+
wallet,
|
|
426
|
+
status: options?.status,
|
|
427
|
+
asset: options?.asset,
|
|
428
|
+
channel_type: options?.channelType,
|
|
429
|
+
pagination: options?.pagination
|
|
430
|
+
? {
|
|
431
|
+
offset: options.pagination.offset,
|
|
432
|
+
limit: options.pagination.limit,
|
|
433
|
+
}
|
|
434
|
+
: undefined,
|
|
435
|
+
};
|
|
436
|
+
const resp = await this.rpcClient.channelsV1GetChannels(req);
|
|
437
|
+
return {
|
|
438
|
+
channels: resp.channels.map(transformChannel),
|
|
439
|
+
metadata: transformPaginationMetadata(resp.metadata),
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
async getHomeChannel(wallet, asset) {
|
|
443
|
+
const req = {
|
|
444
|
+
wallet,
|
|
445
|
+
asset,
|
|
446
|
+
};
|
|
447
|
+
const resp = await this.rpcClient.channelsV1GetHomeChannel(req);
|
|
448
|
+
return transformChannel(resp.channel);
|
|
449
|
+
}
|
|
450
|
+
async getEscrowChannel(escrowChannelId) {
|
|
451
|
+
const req = {
|
|
452
|
+
escrow_channel_id: escrowChannelId,
|
|
453
|
+
};
|
|
454
|
+
const resp = await this.rpcClient.channelsV1GetEscrowChannel(req);
|
|
455
|
+
return transformChannel(resp.channel);
|
|
456
|
+
}
|
|
457
|
+
async getLatestState(wallet, asset, onlySigned) {
|
|
458
|
+
const req = {
|
|
459
|
+
wallet,
|
|
460
|
+
asset,
|
|
461
|
+
only_signed: onlySigned,
|
|
462
|
+
};
|
|
463
|
+
const resp = await this.rpcClient.channelsV1GetLatestState(req);
|
|
464
|
+
return transformState(resp.state);
|
|
465
|
+
}
|
|
466
|
+
async getAppSessions(options) {
|
|
467
|
+
const req = {
|
|
468
|
+
app_session_id: options?.appSessionId,
|
|
469
|
+
participant: options?.wallet,
|
|
470
|
+
status: options?.status,
|
|
471
|
+
pagination: options?.page && options?.pageSize ? {
|
|
472
|
+
offset: (options.page - 1) * options.pageSize,
|
|
473
|
+
limit: options.pageSize,
|
|
474
|
+
} : undefined,
|
|
475
|
+
};
|
|
476
|
+
const resp = await this.rpcClient.appSessionsV1GetAppSessions(req);
|
|
477
|
+
return {
|
|
478
|
+
sessions: resp.app_sessions.map(transformAppSessionInfo),
|
|
479
|
+
metadata: transformPaginationMetadata(resp.metadata),
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
async getAppDefinition(appSessionId) {
|
|
483
|
+
const req = {
|
|
484
|
+
app_session_id: appSessionId,
|
|
485
|
+
};
|
|
486
|
+
const resp = await this.rpcClient.appSessionsV1GetAppDefinition(req);
|
|
487
|
+
return transformAppDefinitionFromRPC(resp.definition);
|
|
488
|
+
}
|
|
489
|
+
async createAppSession(definition, sessionData, quorumSigs) {
|
|
490
|
+
const req = {
|
|
491
|
+
definition: transformAppDefinitionToRPC(definition),
|
|
492
|
+
session_data: sessionData,
|
|
493
|
+
quorum_sigs: quorumSigs,
|
|
494
|
+
};
|
|
495
|
+
const resp = await this.rpcClient.appSessionsV1CreateAppSession(req);
|
|
496
|
+
return {
|
|
497
|
+
appSessionId: resp.app_session_id,
|
|
498
|
+
version: resp.version,
|
|
499
|
+
status: resp.status,
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
async submitAppSessionDeposit(appStateUpdate, quorumSigs, asset, depositAmount) {
|
|
503
|
+
const currentState = await this.getLatestState(this.getUserAddress(), asset, false);
|
|
504
|
+
const newState = nextState(currentState);
|
|
505
|
+
applyCommitTransition(newState, appStateUpdate.appSessionId, depositAmount);
|
|
506
|
+
const appUpdate = transformAppStateUpdateToRPC(appStateUpdate);
|
|
507
|
+
const stateSig = await this.signState(newState);
|
|
508
|
+
newState.userSig = stateSig;
|
|
509
|
+
const req = {
|
|
510
|
+
app_state_update: appUpdate,
|
|
511
|
+
quorum_sigs: quorumSigs,
|
|
512
|
+
user_state: this.transformStateToRPC(newState),
|
|
513
|
+
};
|
|
514
|
+
const resp = await this.rpcClient.appSessionsV1SubmitDepositState(req);
|
|
515
|
+
return resp.signature;
|
|
516
|
+
}
|
|
517
|
+
async submitAppState(appStateUpdate, quorumSigs) {
|
|
518
|
+
const appUpdate = transformAppStateUpdateToRPC(appStateUpdate);
|
|
519
|
+
const req = {
|
|
520
|
+
app_state_update: appUpdate,
|
|
521
|
+
quorum_sigs: quorumSigs,
|
|
522
|
+
};
|
|
523
|
+
await this.rpcClient.appSessionsV1SubmitAppState(req);
|
|
524
|
+
}
|
|
525
|
+
async rebalanceAppSessions(signedUpdates) {
|
|
526
|
+
const rpcUpdates = signedUpdates.map(transformSignedAppStateUpdateToRPC);
|
|
527
|
+
const req = {
|
|
528
|
+
signed_updates: rpcUpdates,
|
|
529
|
+
};
|
|
530
|
+
const resp = await this.rpcClient.appSessionsV1RebalanceAppSessions(req);
|
|
531
|
+
return resp.batch_id;
|
|
532
|
+
}
|
|
533
|
+
async signChannelSessionKeyState(state) {
|
|
534
|
+
const metadataHash = core.getChannelSessionKeyAuthMetadataHashV1(BigInt(state.version), state.assets, BigInt(state.expires_at));
|
|
535
|
+
const packed = core.packChannelKeyStateV1(state.session_key, metadataHash);
|
|
536
|
+
const channelSig = await this.stateSigner.signMessage(packed);
|
|
537
|
+
return stripSignerTypePrefix(channelSig);
|
|
538
|
+
}
|
|
539
|
+
async submitChannelSessionKeyState(state) {
|
|
540
|
+
const req = {
|
|
541
|
+
state,
|
|
542
|
+
};
|
|
543
|
+
await this.rpcClient.channelsV1SubmitSessionKeyState(req);
|
|
544
|
+
}
|
|
545
|
+
async getLastChannelKeyStates(userAddress, sessionKey) {
|
|
546
|
+
const req = {
|
|
547
|
+
user_address: userAddress,
|
|
548
|
+
session_key: sessionKey,
|
|
549
|
+
};
|
|
550
|
+
const resp = await this.rpcClient.channelsV1GetLastKeyStates(req);
|
|
551
|
+
return resp.states;
|
|
552
|
+
}
|
|
553
|
+
async signSessionKeyState(state) {
|
|
554
|
+
const packed = app.packAppSessionKeyStateV1(state);
|
|
555
|
+
const channelSig = await this.stateSigner.signMessage(packed);
|
|
556
|
+
return stripSignerTypePrefix(channelSig);
|
|
557
|
+
}
|
|
558
|
+
async submitSessionKeyState(state) {
|
|
559
|
+
const req = {
|
|
560
|
+
state,
|
|
561
|
+
};
|
|
562
|
+
await this.rpcClient.appSessionsV1SubmitSessionKeyState(req);
|
|
563
|
+
}
|
|
564
|
+
async getLastKeyStates(userAddress, sessionKey) {
|
|
565
|
+
const req = {
|
|
566
|
+
user_address: userAddress,
|
|
567
|
+
session_key: sessionKey,
|
|
568
|
+
};
|
|
569
|
+
const resp = await this.rpcClient.appSessionsV1GetLastKeyStates(req);
|
|
570
|
+
return resp.states;
|
|
571
|
+
}
|
|
572
|
+
async initializeBlockchainClient(chainId) {
|
|
573
|
+
if (this.blockchainClients.has(chainId)) {
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
const rpcUrl = this.config.blockchainRPCs?.get(chainId);
|
|
577
|
+
if (!rpcUrl) {
|
|
578
|
+
throw new Error(`blockchain RPC not configured for chain ${chainId} (use withBlockchainRPC)`);
|
|
579
|
+
}
|
|
580
|
+
const config = await this.getConfig();
|
|
581
|
+
const blockchainInfo = config.blockchains.find((b) => b.id === chainId);
|
|
582
|
+
if (!blockchainInfo) {
|
|
583
|
+
throw new Error(`blockchain ${chainId} not supported by node`);
|
|
584
|
+
}
|
|
585
|
+
const contractAddress = blockchainInfo.channelHubAddress;
|
|
586
|
+
const nodeAddress = config.nodeAddress;
|
|
587
|
+
const publicClient = createPublicClient({
|
|
588
|
+
transport: http(rpcUrl),
|
|
589
|
+
});
|
|
590
|
+
const chain = {
|
|
591
|
+
id: Number(chainId),
|
|
592
|
+
name: `Chain ${chainId}`,
|
|
593
|
+
nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 },
|
|
594
|
+
rpcUrls: {
|
|
595
|
+
default: { http: [rpcUrl] },
|
|
596
|
+
public: { http: [rpcUrl] },
|
|
597
|
+
},
|
|
598
|
+
};
|
|
599
|
+
const isBrowser = typeof window !== 'undefined' && typeof window.ethereum !== 'undefined';
|
|
600
|
+
let walletClient;
|
|
601
|
+
if (isBrowser) {
|
|
602
|
+
walletClient = createWalletClient({
|
|
603
|
+
chain,
|
|
604
|
+
transport: custom(window.ethereum),
|
|
605
|
+
account: this.txSigner.getAddress(),
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
walletClient = createWalletClient({
|
|
610
|
+
chain,
|
|
611
|
+
transport: http(rpcUrl),
|
|
612
|
+
account: this.txSigner.getAddress(),
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
const blockchainClient = new blockchain.evm.Client(contractAddress, publicClient, walletClient, chainId, nodeAddress, this.assetStore);
|
|
616
|
+
this.blockchainClients.set(chainId, blockchainClient);
|
|
617
|
+
}
|
|
618
|
+
buildSigValidatorsBitmap(signerTypes) {
|
|
619
|
+
const bitmap = new Uint8Array(32);
|
|
620
|
+
for (const t of signerTypes) {
|
|
621
|
+
const idx = t & 0xff;
|
|
622
|
+
bitmap[31 - Math.floor(idx / 8)] |= 1 << (idx % 8);
|
|
623
|
+
}
|
|
624
|
+
for (let i = 0; i < 32; i++) {
|
|
625
|
+
if (bitmap[i] !== 0) {
|
|
626
|
+
const hex = Array.from(bitmap.slice(i))
|
|
627
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
628
|
+
.join('');
|
|
629
|
+
return '0x' + hex;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return '0x00';
|
|
633
|
+
}
|
|
634
|
+
async getSupportedSigValidatorsBitmap() {
|
|
635
|
+
const config = await this.getConfig();
|
|
636
|
+
return this.buildSigValidatorsBitmap(config.supportedSigValidators);
|
|
637
|
+
}
|
|
638
|
+
async getNodeAddress() {
|
|
639
|
+
const config = await this.getConfig();
|
|
640
|
+
return config.nodeAddress;
|
|
641
|
+
}
|
|
642
|
+
async submitState(state) {
|
|
643
|
+
const req = {
|
|
644
|
+
state: this.transformStateToRPC(state),
|
|
645
|
+
};
|
|
646
|
+
const resp = await this.rpcClient.channelsV1SubmitState(req);
|
|
647
|
+
return resp.signature;
|
|
648
|
+
}
|
|
649
|
+
async requestChannelCreation(state, channelDef) {
|
|
650
|
+
const req = {
|
|
651
|
+
state: this.transformStateToRPC(state),
|
|
652
|
+
channel_definition: this.transformChannelDefinitionToRPC(channelDef),
|
|
653
|
+
};
|
|
654
|
+
const resp = await this.rpcClient.channelsV1RequestCreation(req);
|
|
655
|
+
return resp.signature;
|
|
656
|
+
}
|
|
657
|
+
transformStateToRPC(state) {
|
|
658
|
+
return {
|
|
659
|
+
id: state.id,
|
|
660
|
+
transition: {
|
|
661
|
+
type: state.transition.type,
|
|
662
|
+
tx_id: state.transition.txId,
|
|
663
|
+
account_id: state.transition.accountId || '',
|
|
664
|
+
amount: state.transition.amount.toString(),
|
|
665
|
+
},
|
|
666
|
+
asset: state.asset,
|
|
667
|
+
user_wallet: state.userWallet,
|
|
668
|
+
epoch: state.epoch.toString(),
|
|
669
|
+
version: state.version.toString(),
|
|
670
|
+
home_channel_id: state.homeChannelId,
|
|
671
|
+
escrow_channel_id: state.escrowChannelId,
|
|
672
|
+
home_ledger: {
|
|
673
|
+
token_address: state.homeLedger.tokenAddress,
|
|
674
|
+
blockchain_id: state.homeLedger.blockchainId.toString(),
|
|
675
|
+
user_balance: state.homeLedger.userBalance.toString(),
|
|
676
|
+
user_net_flow: state.homeLedger.userNetFlow.toString(),
|
|
677
|
+
node_balance: state.homeLedger.nodeBalance.toString(),
|
|
678
|
+
node_net_flow: state.homeLedger.nodeNetFlow.toString(),
|
|
679
|
+
},
|
|
680
|
+
escrow_ledger: state.escrowLedger
|
|
681
|
+
? {
|
|
682
|
+
token_address: state.escrowLedger.tokenAddress,
|
|
683
|
+
blockchain_id: state.escrowLedger.blockchainId.toString(),
|
|
684
|
+
user_balance: state.escrowLedger.userBalance.toString(),
|
|
685
|
+
user_net_flow: state.escrowLedger.userNetFlow.toString(),
|
|
686
|
+
node_balance: state.escrowLedger.nodeBalance.toString(),
|
|
687
|
+
node_net_flow: state.escrowLedger.nodeNetFlow.toString(),
|
|
688
|
+
}
|
|
689
|
+
: undefined,
|
|
690
|
+
user_sig: state.userSig,
|
|
691
|
+
node_sig: state.nodeSig,
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
transformChannelDefinitionToRPC(def) {
|
|
695
|
+
return {
|
|
696
|
+
nonce: def.nonce.toString(),
|
|
697
|
+
challenge: def.challenge,
|
|
698
|
+
approved_sig_validators: def.approvedSigValidators,
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
transitionTypeToString(type) {
|
|
702
|
+
const typeMap = {
|
|
703
|
+
[core.TransitionType.Void]: 'void',
|
|
704
|
+
[core.TransitionType.Acknowledgement]: 'acknowledgement',
|
|
705
|
+
[core.TransitionType.HomeDeposit]: 'home_deposit',
|
|
706
|
+
[core.TransitionType.HomeWithdrawal]: 'home_withdrawal',
|
|
707
|
+
[core.TransitionType.EscrowDeposit]: 'escrow_deposit',
|
|
708
|
+
[core.TransitionType.EscrowWithdraw]: 'escrow_withdraw',
|
|
709
|
+
[core.TransitionType.TransferSend]: 'transfer_send',
|
|
710
|
+
[core.TransitionType.TransferReceive]: 'transfer_receive',
|
|
711
|
+
[core.TransitionType.Commit]: 'commit',
|
|
712
|
+
[core.TransitionType.Release]: 'release',
|
|
713
|
+
[core.TransitionType.Migrate]: 'migrate',
|
|
714
|
+
[core.TransitionType.EscrowLock]: 'escrow_lock',
|
|
715
|
+
[core.TransitionType.MutualLock]: 'mutual_lock',
|
|
716
|
+
[core.TransitionType.Finalize]: 'finalize',
|
|
717
|
+
};
|
|
718
|
+
return typeMap[type] || 'home_deposit';
|
|
719
|
+
}
|
|
720
|
+
}
|