@4mica/sdk 0.5.0 → 0.5.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/README.md +251 -395
- package/package.json +36 -3
- package/.eslintrc.cjs +0 -29
- package/.github/workflows/ci.yml +0 -31
- package/.prettierignore +0 -3
- package/.prettierrc +0 -6
- package/eslint.config.mjs +0 -22
- package/src/abi/core4mica.json +0 -1605
- package/src/abi/erc20.json +0 -187
- package/src/auth.ts +0 -325
- package/src/bls.ts +0 -76
- package/src/client.ts +0 -347
- package/src/config.ts +0 -149
- package/src/contract.ts +0 -194
- package/src/errors.ts +0 -40
- package/src/guarantee.ts +0 -96
- package/src/index.ts +0 -12
- package/src/models.ts +0 -309
- package/src/rpc.ts +0 -225
- package/src/signing.ts +0 -330
- package/src/utils.ts +0 -75
- package/src/x402/index.ts +0 -192
- package/src/x402/models.ts +0 -94
- package/tests/auth.integration.test.ts +0 -97
- package/tests/auth.test.ts +0 -292
- package/tests/config.test.ts +0 -56
- package/tests/guarantee.test.ts +0 -26
- package/tests/rpc.test.ts +0 -76
- package/tests/signing.test.ts +0 -52
- package/tests/utils.test.ts +0 -35
- package/tests/x402.test.ts +0 -152
- package/tsconfig.build.json +0 -5
- package/tsconfig.json +0 -15
- package/vitest.config.ts +0 -12
package/src/client.ts
DELETED
|
@@ -1,347 +0,0 @@
|
|
|
1
|
-
import { signatureToWords } from './bls';
|
|
2
|
-
import { AuthSession } from './auth';
|
|
3
|
-
import type { AuthTokens } from './auth';
|
|
4
|
-
import { Config } from './config';
|
|
5
|
-
import { ContractGateway } from './contract';
|
|
6
|
-
import { AuthMissingConfigError, ClientInitializationError, VerificationError } from './errors';
|
|
7
|
-
import { decodeGuaranteeClaims } from './guarantee';
|
|
8
|
-
import {
|
|
9
|
-
AssetBalanceInfo,
|
|
10
|
-
BLSCert,
|
|
11
|
-
CollateralEventInfo,
|
|
12
|
-
GuaranteeInfo,
|
|
13
|
-
PaymentGuaranteeClaims,
|
|
14
|
-
PaymentGuaranteeRequestClaims,
|
|
15
|
-
PaymentSignature,
|
|
16
|
-
PendingRemunerationInfo,
|
|
17
|
-
RecipientPaymentInfo,
|
|
18
|
-
SigningScheme,
|
|
19
|
-
TabInfo,
|
|
20
|
-
TabPaymentStatus,
|
|
21
|
-
UserInfo,
|
|
22
|
-
} from './models';
|
|
23
|
-
import { RpcProxy } from './rpc';
|
|
24
|
-
import { CorePublicParameters, PaymentSigner } from './signing';
|
|
25
|
-
import { normalizeAddress, parseU256, serializeU256 } from './utils';
|
|
26
|
-
|
|
27
|
-
const isNumericLike = (value: unknown): value is number | bigint | string =>
|
|
28
|
-
typeof value === 'number' || typeof value === 'bigint' || typeof value === 'string';
|
|
29
|
-
|
|
30
|
-
type RpcTabStatus = {
|
|
31
|
-
paid?: number | bigint | string;
|
|
32
|
-
paidAmount?: number | bigint | string;
|
|
33
|
-
remunerated?: boolean;
|
|
34
|
-
paidOut?: boolean;
|
|
35
|
-
asset?: string;
|
|
36
|
-
assetAddress?: string;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
function tabStatusFromRpc(status: RpcTabStatus): TabPaymentStatus {
|
|
40
|
-
const paid = status.paid !== undefined ? status.paid : (status.paidAmount ?? 0);
|
|
41
|
-
const remunerated = status.remunerated ?? status.paidOut ?? false;
|
|
42
|
-
const asset = status.asset ?? status.assetAddress ?? '';
|
|
43
|
-
return {
|
|
44
|
-
paid: parseU256(paid),
|
|
45
|
-
remunerated: Boolean(remunerated),
|
|
46
|
-
asset,
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export class Client {
|
|
51
|
-
readonly rpc: RpcProxy;
|
|
52
|
-
readonly params: CorePublicParameters;
|
|
53
|
-
readonly gateway: ContractGateway;
|
|
54
|
-
readonly guaranteeDomain: string;
|
|
55
|
-
readonly user: UserClient;
|
|
56
|
-
readonly recipient: RecipientClient;
|
|
57
|
-
readonly signer: PaymentSigner;
|
|
58
|
-
private authSession?: AuthSession;
|
|
59
|
-
|
|
60
|
-
private constructor(
|
|
61
|
-
rpc: RpcProxy,
|
|
62
|
-
params: CorePublicParameters,
|
|
63
|
-
gateway: ContractGateway,
|
|
64
|
-
guaranteeDomain: string,
|
|
65
|
-
signer: PaymentSigner,
|
|
66
|
-
authSession?: AuthSession
|
|
67
|
-
) {
|
|
68
|
-
this.rpc = rpc;
|
|
69
|
-
this.params = params;
|
|
70
|
-
this.gateway = gateway;
|
|
71
|
-
this.guaranteeDomain = guaranteeDomain;
|
|
72
|
-
this.signer = signer;
|
|
73
|
-
this.authSession = authSession;
|
|
74
|
-
this.user = new UserClient(this);
|
|
75
|
-
this.recipient = new RecipientClient(this);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
static async new(cfg: Config): Promise<Client> {
|
|
79
|
-
const rpc = new RpcProxy(cfg.rpcUrl, cfg.adminApiKey);
|
|
80
|
-
const params = await rpc.getPublicParams();
|
|
81
|
-
const gateway = Client.buildGateway(cfg, params);
|
|
82
|
-
await Client.validateChainId(gateway, params.chainId);
|
|
83
|
-
const guaranteeDomain = await gateway.getGuaranteeDomain();
|
|
84
|
-
const signer = new PaymentSigner(cfg.signer);
|
|
85
|
-
const authEnabled = cfg.authUrl !== undefined || cfg.authRefreshMarginSecs !== undefined;
|
|
86
|
-
const authSession =
|
|
87
|
-
cfg.bearerToken || !authEnabled
|
|
88
|
-
? undefined
|
|
89
|
-
: new AuthSession({
|
|
90
|
-
authUrl: cfg.authUrl ?? cfg.rpcUrl,
|
|
91
|
-
signer: cfg.signer,
|
|
92
|
-
refreshMarginSecs: cfg.authRefreshMarginSecs ?? 60,
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
if (cfg.bearerToken) {
|
|
96
|
-
rpc.withBearerToken(cfg.bearerToken);
|
|
97
|
-
} else if (authSession) {
|
|
98
|
-
rpc.withTokenProvider(() => authSession.accessToken());
|
|
99
|
-
}
|
|
100
|
-
return new Client(rpc, params, gateway, guaranteeDomain, signer, authSession);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
private static buildGateway(cfg: Config, params: CorePublicParameters): ContractGateway {
|
|
104
|
-
const ethRpcUrl = cfg.ethereumHttpRpcUrl ?? params.ethereumHttpRpcUrl;
|
|
105
|
-
const contractAddress = cfg.contractAddress ?? params.contractAddress;
|
|
106
|
-
return new ContractGateway(ethRpcUrl, cfg.walletPrivateKey, contractAddress, params.chainId);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
private static async validateChainId(
|
|
110
|
-
gateway: ContractGateway,
|
|
111
|
-
expectedChainId: number
|
|
112
|
-
): Promise<void> {
|
|
113
|
-
try {
|
|
114
|
-
const chainId = await gateway.getChainId();
|
|
115
|
-
if (Number(chainId) !== Number(expectedChainId)) {
|
|
116
|
-
throw new ClientInitializationError(
|
|
117
|
-
`chain id mismatch between core (${expectedChainId}) and provider (${chainId})`
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
} catch (err: unknown) {
|
|
121
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
122
|
-
throw new ClientInitializationError(message);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async aclose(): Promise<void> {
|
|
127
|
-
await this.rpc.aclose();
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
async login(): Promise<AuthTokens> {
|
|
131
|
-
if (!this.authSession) {
|
|
132
|
-
throw new AuthMissingConfigError('auth is not enabled');
|
|
133
|
-
}
|
|
134
|
-
return this.authSession.login();
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export class UserClient {
|
|
139
|
-
constructor(private client: Client) {}
|
|
140
|
-
|
|
141
|
-
get guaranteeDomain(): string {
|
|
142
|
-
return this.client.guaranteeDomain;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
async approveErc20(token: string, amount: number | bigint | string) {
|
|
146
|
-
return this.client.gateway.approveErc20(token, amount);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async deposit(amount: number | bigint | string, erc20Token?: string) {
|
|
150
|
-
return this.client.gateway.deposit(amount, erc20Token);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
async getUser(): Promise<UserInfo[]> {
|
|
154
|
-
const assets = await this.client.gateway.getUserAssets();
|
|
155
|
-
return assets.map(
|
|
156
|
-
(a) =>
|
|
157
|
-
({
|
|
158
|
-
asset: a.asset,
|
|
159
|
-
collateral: parseU256(a.collateral),
|
|
160
|
-
withdrawalRequestAmount: parseU256(a.withdrawal_request_amount),
|
|
161
|
-
withdrawalRequestTimestamp: Number(a.withdrawal_request_timestamp),
|
|
162
|
-
}) satisfies UserInfo
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
async getTabPaymentStatus(tabId: number | bigint): Promise<TabPaymentStatus> {
|
|
167
|
-
const status = await this.client.gateway.getPaymentStatus(tabId);
|
|
168
|
-
return tabStatusFromRpc(status);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
async signPayment(
|
|
172
|
-
claims: PaymentGuaranteeRequestClaims,
|
|
173
|
-
scheme: SigningScheme = SigningScheme.EIP712
|
|
174
|
-
): Promise<PaymentSignature> {
|
|
175
|
-
return this.client.signer.signRequest(this.client.params, claims, scheme);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
async payTab(
|
|
179
|
-
tabId: number | bigint,
|
|
180
|
-
reqId: number | bigint,
|
|
181
|
-
amount: number | bigint | string,
|
|
182
|
-
recipientAddress: string,
|
|
183
|
-
erc20Token?: string
|
|
184
|
-
) {
|
|
185
|
-
if (erc20Token) {
|
|
186
|
-
return this.client.gateway.payTabErc20(tabId, amount, erc20Token, recipientAddress);
|
|
187
|
-
}
|
|
188
|
-
return this.client.gateway.payTabEth(tabId, reqId, amount, recipientAddress);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
async requestWithdrawal(amount: number | bigint | string, erc20Token?: string) {
|
|
192
|
-
return this.client.gateway.requestWithdrawal(amount, erc20Token);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async cancelWithdrawal(erc20Token?: string) {
|
|
196
|
-
return this.client.gateway.cancelWithdrawal(erc20Token);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
async finalizeWithdrawal(erc20Token?: string) {
|
|
200
|
-
return this.client.gateway.finalizeWithdrawal(erc20Token);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export class RecipientClient {
|
|
205
|
-
constructor(private client: Client) {}
|
|
206
|
-
|
|
207
|
-
private get recipientAddress(): string {
|
|
208
|
-
return normalizeAddress(this.client.gateway.wallet.address);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
get guaranteeDomain(): string {
|
|
212
|
-
return this.client.guaranteeDomain;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
private checkSigner(expected: string): void {
|
|
216
|
-
if (normalizeAddress(expected) !== this.recipientAddress) {
|
|
217
|
-
throw new VerificationError('signer address does not match recipient address');
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
async createTab(
|
|
222
|
-
userAddress: string,
|
|
223
|
-
recipientAddress: string,
|
|
224
|
-
erc20Token: string | undefined | null,
|
|
225
|
-
ttl?: number | null
|
|
226
|
-
): Promise<bigint> {
|
|
227
|
-
this.checkSigner(recipientAddress);
|
|
228
|
-
const body = {
|
|
229
|
-
user_address: normalizeAddress(userAddress),
|
|
230
|
-
recipient_address: normalizeAddress(recipientAddress),
|
|
231
|
-
erc20_token: erc20Token ? normalizeAddress(erc20Token) : null,
|
|
232
|
-
ttl: ttl ?? null,
|
|
233
|
-
};
|
|
234
|
-
const result = await this.client.rpc.createPaymentTab(body);
|
|
235
|
-
const record = result as Record<string, unknown>;
|
|
236
|
-
const tabIdRaw = record.id ?? record.tabId ?? record.tab_id;
|
|
237
|
-
const tabId = isNumericLike(tabIdRaw) ? tabIdRaw : 0;
|
|
238
|
-
return parseU256(tabId);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
async getTabPaymentStatus(tabId: number | bigint): Promise<TabPaymentStatus> {
|
|
242
|
-
const status = await this.client.gateway.getPaymentStatus(tabId);
|
|
243
|
-
return tabStatusFromRpc(status);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
async issuePaymentGuarantee(
|
|
247
|
-
claims: PaymentGuaranteeRequestClaims,
|
|
248
|
-
signature: string,
|
|
249
|
-
scheme: SigningScheme
|
|
250
|
-
): Promise<BLSCert> {
|
|
251
|
-
this.checkSigner(claims.recipientAddress);
|
|
252
|
-
const payload = {
|
|
253
|
-
claims: {
|
|
254
|
-
version: 'v1',
|
|
255
|
-
user_address: claims.userAddress,
|
|
256
|
-
recipient_address: claims.recipientAddress,
|
|
257
|
-
tab_id: serializeU256(claims.tabId),
|
|
258
|
-
req_id: serializeU256(claims.reqId),
|
|
259
|
-
amount: serializeU256(claims.amount),
|
|
260
|
-
asset_address: claims.assetAddress,
|
|
261
|
-
timestamp: Number(claims.timestamp),
|
|
262
|
-
},
|
|
263
|
-
signature,
|
|
264
|
-
scheme,
|
|
265
|
-
};
|
|
266
|
-
const cert = await this.client.rpc.issueGuarantee(payload);
|
|
267
|
-
const record = cert as Record<string, unknown>;
|
|
268
|
-
const certClaims = typeof record.claims === 'string' ? record.claims : '';
|
|
269
|
-
const signatureOut = typeof record.signature === 'string' ? record.signature : '';
|
|
270
|
-
return { claims: certClaims, signature: signatureOut };
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
verifyPaymentGuarantee(cert: BLSCert): PaymentGuaranteeClaims {
|
|
274
|
-
const claims = decodeGuaranteeClaims(cert.claims);
|
|
275
|
-
const domainHex = this.guaranteeDomain.startsWith('0x')
|
|
276
|
-
? this.guaranteeDomain.slice(2)
|
|
277
|
-
: Buffer.from(this.guaranteeDomain).toString('hex');
|
|
278
|
-
const claimsHex = Buffer.from(claims.domain).toString('hex');
|
|
279
|
-
if (claimsHex !== domainHex) {
|
|
280
|
-
throw new VerificationError('guarantee domain mismatch');
|
|
281
|
-
}
|
|
282
|
-
return claims;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
async remunerate(cert: BLSCert) {
|
|
286
|
-
this.verifyPaymentGuarantee(cert);
|
|
287
|
-
const sigWords = signatureToWords(cert.signature);
|
|
288
|
-
const claimsBytes = Buffer.from(cert.claims.replace(/^0x/, ''), 'hex');
|
|
289
|
-
return this.client.gateway.remunerate(claimsBytes, sigWords);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
async listSettledTabs(): Promise<TabInfo[]> {
|
|
293
|
-
const tabs = await this.client.rpc.listSettledTabs(this.recipientAddress);
|
|
294
|
-
return tabs.map((t) => TabInfo.fromRpc(t));
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
async listPendingRemunerations(): Promise<PendingRemunerationInfo[]> {
|
|
298
|
-
const items = await this.client.rpc.listPendingRemunerations(this.recipientAddress);
|
|
299
|
-
return items.map((item) => PendingRemunerationInfo.fromRpc(item));
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
async getTab(tabId: number | bigint): Promise<TabInfo | null> {
|
|
303
|
-
const result = await this.client.rpc.getTab(tabId);
|
|
304
|
-
return result ? TabInfo.fromRpc(result) : null;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
async listRecipientTabs(settlementStatuses?: string[]): Promise<TabInfo[]> {
|
|
308
|
-
const tabs = await this.client.rpc.listRecipientTabs(this.recipientAddress, settlementStatuses);
|
|
309
|
-
return tabs.map((t) => TabInfo.fromRpc(t));
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
async getTabGuarantees(tabId: number | bigint): Promise<GuaranteeInfo[]> {
|
|
313
|
-
const guarantees = await this.client.rpc.getTabGuarantees(tabId);
|
|
314
|
-
return guarantees.map((g) => GuaranteeInfo.fromRpc(g));
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
async getLatestGuarantee(tabId: number | bigint): Promise<GuaranteeInfo | null> {
|
|
318
|
-
const result = await this.client.rpc.getLatestGuarantee(tabId);
|
|
319
|
-
return result ? GuaranteeInfo.fromRpc(result) : null;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
async getGuarantee(
|
|
323
|
-
tabId: number | bigint,
|
|
324
|
-
reqId: number | bigint
|
|
325
|
-
): Promise<GuaranteeInfo | null> {
|
|
326
|
-
const result = await this.client.rpc.getGuarantee(tabId, reqId);
|
|
327
|
-
return result ? GuaranteeInfo.fromRpc(result) : null;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
async listRecipientPayments(): Promise<RecipientPaymentInfo[]> {
|
|
331
|
-
const payments = await this.client.rpc.listRecipientPayments(this.recipientAddress);
|
|
332
|
-
return payments.map((p) => RecipientPaymentInfo.fromRpc(p));
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
async getCollateralEventsForTab(tabId: number | bigint): Promise<CollateralEventInfo[]> {
|
|
336
|
-
const events = await this.client.rpc.getCollateralEventsForTab(tabId);
|
|
337
|
-
return events.map((ev) => CollateralEventInfo.fromRpc(ev));
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
async getUserAssetBalance(
|
|
341
|
-
userAddress: string,
|
|
342
|
-
assetAddress: string
|
|
343
|
-
): Promise<AssetBalanceInfo | null> {
|
|
344
|
-
const balance = await this.client.rpc.getUserAssetBalance(userAddress, assetAddress);
|
|
345
|
-
return balance ? AssetBalanceInfo.fromRpc(balance) : null;
|
|
346
|
-
}
|
|
347
|
-
}
|
package/src/config.ts
DELETED
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import { ConfigError } from './errors';
|
|
2
|
-
import { createLocalSigner, EvmSigner } from './signing';
|
|
3
|
-
import { ValidationError, normalizeAddress, normalizePrivateKey, validateUrl } from './utils';
|
|
4
|
-
|
|
5
|
-
export interface Config {
|
|
6
|
-
rpcUrl: string;
|
|
7
|
-
walletPrivateKey: string;
|
|
8
|
-
signer: EvmSigner;
|
|
9
|
-
ethereumHttpRpcUrl?: string;
|
|
10
|
-
contractAddress?: string;
|
|
11
|
-
adminApiKey?: string;
|
|
12
|
-
bearerToken?: string;
|
|
13
|
-
authUrl?: string;
|
|
14
|
-
authRefreshMarginSecs?: number;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export class ConfigBuilder {
|
|
18
|
-
private _rpcUrl: string | undefined = 'http://127.0.0.1:3000';
|
|
19
|
-
private _walletPrivateKey: string | undefined;
|
|
20
|
-
private _signer: EvmSigner | undefined;
|
|
21
|
-
private _ethereumHttpRpcUrl?: string;
|
|
22
|
-
private _contractAddress?: string;
|
|
23
|
-
private _adminApiKey?: string;
|
|
24
|
-
private _bearerToken?: string;
|
|
25
|
-
private _authEnabled = false;
|
|
26
|
-
private _authUrl?: string;
|
|
27
|
-
private _authRefreshMarginSecs?: number;
|
|
28
|
-
|
|
29
|
-
rpcUrl(value: string): ConfigBuilder {
|
|
30
|
-
this._rpcUrl = value;
|
|
31
|
-
return this;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
walletPrivateKey(value: string): ConfigBuilder {
|
|
35
|
-
this._walletPrivateKey = value;
|
|
36
|
-
return this;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
signer(value: EvmSigner): ConfigBuilder {
|
|
40
|
-
this._signer = value;
|
|
41
|
-
return this;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
ethereumHttpRpcUrl(value: string): ConfigBuilder {
|
|
45
|
-
this._ethereumHttpRpcUrl = value;
|
|
46
|
-
return this;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
contractAddress(value: string): ConfigBuilder {
|
|
50
|
-
this._contractAddress = value;
|
|
51
|
-
return this;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
adminApiKey(value: string): ConfigBuilder {
|
|
55
|
-
this._adminApiKey = value;
|
|
56
|
-
return this;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
bearerToken(value: string): ConfigBuilder {
|
|
60
|
-
this._bearerToken = value;
|
|
61
|
-
return this;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
enableAuth(): ConfigBuilder {
|
|
65
|
-
this._authEnabled = true;
|
|
66
|
-
return this;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
authUrl(value: string): ConfigBuilder {
|
|
70
|
-
this._authUrl = value;
|
|
71
|
-
this._authEnabled = true;
|
|
72
|
-
return this;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
authRefreshMarginSecs(value: number): ConfigBuilder {
|
|
76
|
-
this._authRefreshMarginSecs = value;
|
|
77
|
-
this._authEnabled = true;
|
|
78
|
-
return this;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
fromEnv(): ConfigBuilder {
|
|
82
|
-
const env = process.env;
|
|
83
|
-
if (env['4MICA_RPC_URL']) this._rpcUrl = env['4MICA_RPC_URL'];
|
|
84
|
-
if (env['4MICA_WALLET_PRIVATE_KEY']) this._walletPrivateKey = env['4MICA_WALLET_PRIVATE_KEY'];
|
|
85
|
-
if (env['4MICA_ETHEREUM_HTTP_RPC_URL'])
|
|
86
|
-
this._ethereumHttpRpcUrl = env['4MICA_ETHEREUM_HTTP_RPC_URL'];
|
|
87
|
-
if (env['4MICA_CONTRACT_ADDRESS']) this._contractAddress = env['4MICA_CONTRACT_ADDRESS'];
|
|
88
|
-
if (env['4MICA_ADMIN_API_KEY']) this._adminApiKey = env['4MICA_ADMIN_API_KEY'];
|
|
89
|
-
if (env['4MICA_BEARER_TOKEN']) this._bearerToken = env['4MICA_BEARER_TOKEN'];
|
|
90
|
-
if (env['4MICA_AUTH_URL']) {
|
|
91
|
-
this._authUrl = env['4MICA_AUTH_URL'];
|
|
92
|
-
this._authEnabled = true;
|
|
93
|
-
}
|
|
94
|
-
if (env['4MICA_AUTH_REFRESH_MARGIN_SECS']) {
|
|
95
|
-
this._authRefreshMarginSecs = Number(env['4MICA_AUTH_REFRESH_MARGIN_SECS']);
|
|
96
|
-
this._authEnabled = true;
|
|
97
|
-
}
|
|
98
|
-
return this;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
build(): Config {
|
|
102
|
-
if (!this._signer && !this._walletPrivateKey) {
|
|
103
|
-
throw new ConfigError('missing signer or wallet_private_key');
|
|
104
|
-
}
|
|
105
|
-
if (!this._rpcUrl) {
|
|
106
|
-
throw new ConfigError('missing rpc_url');
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
try {
|
|
110
|
-
const rpcUrl = validateUrl(this._rpcUrl);
|
|
111
|
-
const walletPrivateKey = this._walletPrivateKey
|
|
112
|
-
? normalizePrivateKey(this._walletPrivateKey)
|
|
113
|
-
: undefined;
|
|
114
|
-
|
|
115
|
-
const signer: EvmSigner = this._signer ?? createLocalSigner(walletPrivateKey!);
|
|
116
|
-
|
|
117
|
-
const ethereumHttpRpcUrl = this._ethereumHttpRpcUrl
|
|
118
|
-
? validateUrl(this._ethereumHttpRpcUrl)
|
|
119
|
-
: undefined;
|
|
120
|
-
const contractAddress = this._contractAddress
|
|
121
|
-
? normalizeAddress(this._contractAddress)
|
|
122
|
-
: undefined;
|
|
123
|
-
const authUrl = this._authUrl ? validateUrl(this._authUrl) : undefined;
|
|
124
|
-
const refreshMargin =
|
|
125
|
-
this._authRefreshMarginSecs !== undefined ? this._authRefreshMarginSecs : 60;
|
|
126
|
-
if (!Number.isFinite(refreshMargin) || refreshMargin < 0) {
|
|
127
|
-
throw new ValidationError('invalid auth refresh margin');
|
|
128
|
-
}
|
|
129
|
-
const authEnabled = this._authEnabled;
|
|
130
|
-
|
|
131
|
-
return {
|
|
132
|
-
rpcUrl,
|
|
133
|
-
walletPrivateKey: walletPrivateKey ?? signer.address,
|
|
134
|
-
signer,
|
|
135
|
-
ethereumHttpRpcUrl,
|
|
136
|
-
contractAddress,
|
|
137
|
-
adminApiKey: this._adminApiKey,
|
|
138
|
-
bearerToken: this._bearerToken,
|
|
139
|
-
authUrl: authEnabled ? (authUrl ?? rpcUrl) : undefined,
|
|
140
|
-
authRefreshMarginSecs: authEnabled ? refreshMargin : undefined,
|
|
141
|
-
};
|
|
142
|
-
} catch (err) {
|
|
143
|
-
if (err instanceof ValidationError) {
|
|
144
|
-
throw new ConfigError(err.message);
|
|
145
|
-
}
|
|
146
|
-
throw err;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
package/src/contract.ts
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Contract,
|
|
3
|
-
InterfaceAbi,
|
|
4
|
-
JsonRpcProvider,
|
|
5
|
-
Wallet,
|
|
6
|
-
getBytes,
|
|
7
|
-
hexlify,
|
|
8
|
-
toBeHex,
|
|
9
|
-
} from 'ethers';
|
|
10
|
-
import core4micaAbi from './abi/core4mica.json';
|
|
11
|
-
import erc20Abi from './abi/erc20.json';
|
|
12
|
-
import { ContractError } from './errors';
|
|
13
|
-
import { parseU256 } from './utils';
|
|
14
|
-
|
|
15
|
-
type ContractFactory = (address: string, abi: unknown, signer: Wallet) => Contract;
|
|
16
|
-
|
|
17
|
-
export class ContractGateway {
|
|
18
|
-
readonly provider: JsonRpcProvider;
|
|
19
|
-
readonly wallet: Wallet;
|
|
20
|
-
readonly contract: Contract;
|
|
21
|
-
private erc20Cache: Map<string, Contract> = new Map();
|
|
22
|
-
|
|
23
|
-
constructor(
|
|
24
|
-
ethRpcUrl: string,
|
|
25
|
-
privateKey: string,
|
|
26
|
-
contractAddress: string,
|
|
27
|
-
chainId: number | bigint,
|
|
28
|
-
provider?: JsonRpcProvider,
|
|
29
|
-
contractFactory?: ContractFactory
|
|
30
|
-
) {
|
|
31
|
-
const parsedChainId = Number(chainId);
|
|
32
|
-
if (!Number.isFinite(parsedChainId)) {
|
|
33
|
-
throw new ContractError(`invalid chain id: ${chainId}`);
|
|
34
|
-
}
|
|
35
|
-
const networkish = { chainId: parsedChainId, name: `chain-${parsedChainId}` };
|
|
36
|
-
this.provider = provider ?? new JsonRpcProvider(ethRpcUrl, networkish);
|
|
37
|
-
this.wallet = new Wallet(privateKey, this.provider);
|
|
38
|
-
const factory: ContractFactory =
|
|
39
|
-
contractFactory ??
|
|
40
|
-
((addr, abi, signer) => {
|
|
41
|
-
const resolvedAbi = (abi as { abi?: InterfaceAbi }).abi ?? (abi as InterfaceAbi);
|
|
42
|
-
return new Contract(addr, resolvedAbi as InterfaceAbi, signer);
|
|
43
|
-
});
|
|
44
|
-
this.contract = factory(contractAddress, core4micaAbi, this.wallet);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async getChainId(): Promise<number> {
|
|
48
|
-
const network = await this.provider.getNetwork();
|
|
49
|
-
return Number(network.chainId);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
private erc20(address: string): Contract {
|
|
53
|
-
const checksum = address;
|
|
54
|
-
if (!this.erc20Cache.has(checksum)) {
|
|
55
|
-
this.erc20Cache.set(checksum, new Contract(checksum, erc20Abi.abi ?? erc20Abi, this.wallet));
|
|
56
|
-
}
|
|
57
|
-
return this.erc20Cache.get(checksum)!;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
private async send<T>(promise: Promise<unknown>): Promise<T> {
|
|
61
|
-
try {
|
|
62
|
-
const tx = await promise;
|
|
63
|
-
if (typeof (tx as { wait?: unknown }).wait === 'function') {
|
|
64
|
-
return await (tx as { wait: () => Promise<T> }).wait();
|
|
65
|
-
}
|
|
66
|
-
return tx as T;
|
|
67
|
-
} catch (err: unknown) {
|
|
68
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
69
|
-
throw new ContractError(message);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async getGuaranteeDomain(): Promise<string> {
|
|
74
|
-
return this.send<string>(this.contract.guaranteeDomainSeparator());
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async approveErc20(token: string, amount: number | bigint | string): Promise<unknown> {
|
|
78
|
-
const erc20 = this.erc20(token);
|
|
79
|
-
const { target, address } = this.contract as { target?: string; address?: string };
|
|
80
|
-
const spender = target ?? address ?? token;
|
|
81
|
-
return this.send(erc20.approve(spender, parseU256(amount)));
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
async deposit(amount: number | bigint | string, erc20Token?: string): Promise<unknown> {
|
|
85
|
-
if (erc20Token) {
|
|
86
|
-
return this.send(this.contract.depositStablecoin(erc20Token, parseU256(amount)));
|
|
87
|
-
}
|
|
88
|
-
return this.send(
|
|
89
|
-
this.contract.deposit({
|
|
90
|
-
value: parseU256(amount),
|
|
91
|
-
})
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
async getUserAssets(): Promise<
|
|
96
|
-
{
|
|
97
|
-
asset: string;
|
|
98
|
-
collateral: bigint;
|
|
99
|
-
withdrawal_request_timestamp: bigint;
|
|
100
|
-
withdrawal_request_amount: bigint;
|
|
101
|
-
}[]
|
|
102
|
-
> {
|
|
103
|
-
try {
|
|
104
|
-
const result = await this.contract.getUserAllAssets(this.wallet.address);
|
|
105
|
-
return (
|
|
106
|
-
result as Array<
|
|
107
|
-
[string, number | bigint | string, number | bigint | string, number | bigint | string]
|
|
108
|
-
>
|
|
109
|
-
).map((asset) => ({
|
|
110
|
-
asset: asset[0],
|
|
111
|
-
collateral: parseU256(asset[1]),
|
|
112
|
-
withdrawal_request_timestamp: parseU256(asset[2]),
|
|
113
|
-
withdrawal_request_amount: parseU256(asset[3]),
|
|
114
|
-
}));
|
|
115
|
-
} catch (err: unknown) {
|
|
116
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
117
|
-
throw new ContractError(message);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
async getPaymentStatus(tabId: number | bigint): Promise<{
|
|
122
|
-
paid: bigint;
|
|
123
|
-
remunerated: boolean;
|
|
124
|
-
asset: string;
|
|
125
|
-
}> {
|
|
126
|
-
try {
|
|
127
|
-
const [paid, remunerated, asset] = await this.contract.getPaymentStatus(parseU256(tabId));
|
|
128
|
-
return {
|
|
129
|
-
paid: parseU256(paid),
|
|
130
|
-
remunerated: Boolean(remunerated),
|
|
131
|
-
asset,
|
|
132
|
-
};
|
|
133
|
-
} catch (err: unknown) {
|
|
134
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
135
|
-
throw new ContractError(message);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
async payTabEth(
|
|
140
|
-
tabId: number | bigint,
|
|
141
|
-
reqId: number | bigint,
|
|
142
|
-
amount: number | bigint | string,
|
|
143
|
-
recipient: string
|
|
144
|
-
): Promise<unknown> {
|
|
145
|
-
const data = Buffer.from(
|
|
146
|
-
`tab_id:${toBeHex(parseU256(tabId))};req_id:${toBeHex(parseU256(reqId))}`
|
|
147
|
-
);
|
|
148
|
-
const tx = {
|
|
149
|
-
to: recipient,
|
|
150
|
-
value: parseU256(amount),
|
|
151
|
-
data: hexlify(data),
|
|
152
|
-
};
|
|
153
|
-
return this.send(this.wallet.sendTransaction(tx));
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
async payTabErc20(
|
|
157
|
-
tabId: number | bigint,
|
|
158
|
-
amount: number | bigint | string,
|
|
159
|
-
erc20Token: string,
|
|
160
|
-
recipient: string
|
|
161
|
-
): Promise<unknown> {
|
|
162
|
-
return this.send(
|
|
163
|
-
this.contract.payTabInERC20Token(parseU256(tabId), erc20Token, parseU256(amount), recipient)
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
async requestWithdrawal(amount: number | bigint | string, erc20Token?: string): Promise<unknown> {
|
|
168
|
-
if (erc20Token) {
|
|
169
|
-
return this.send(
|
|
170
|
-
this.contract['requestWithdrawal(address,uint256)'](erc20Token, parseU256(amount))
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
return this.send(this.contract['requestWithdrawal(uint256)'](parseU256(amount)));
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
async cancelWithdrawal(erc20Token?: string): Promise<unknown> {
|
|
177
|
-
if (erc20Token) {
|
|
178
|
-
return this.send(this.contract['cancelWithdrawal(address)'](erc20Token));
|
|
179
|
-
}
|
|
180
|
-
return this.send(this.contract['cancelWithdrawal()']());
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
async finalizeWithdrawal(erc20Token?: string): Promise<unknown> {
|
|
184
|
-
if (erc20Token) {
|
|
185
|
-
return this.send(this.contract['finalizeWithdrawal(address)'](erc20Token));
|
|
186
|
-
}
|
|
187
|
-
return this.send(this.contract['finalizeWithdrawal()']());
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
async remunerate(claimsBlob: Uint8Array, signatureWords: Uint8Array[]): Promise<unknown> {
|
|
191
|
-
const sigStruct = signatureWords.map((word) => hexlify(getBytes(word)));
|
|
192
|
-
return this.send(this.contract.remunerate(hexlify(claimsBlob), sigStruct));
|
|
193
|
-
}
|
|
194
|
-
}
|