crumb-alpha-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-test.log +34 -0
- package/dist/balance.d.ts +5 -0
- package/dist/balance.d.ts.map +1 -0
- package/dist/balance.js +16 -0
- package/dist/balance.js.map +1 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +11 -0
- package/dist/errors.js.map +1 -0
- package/dist/gateway.d.ts +6 -0
- package/dist/gateway.d.ts.map +1 -0
- package/dist/gateway.js +6 -0
- package/dist/gateway.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/payment402.d.ts +2 -0
- package/dist/payment402.d.ts.map +1 -0
- package/dist/payment402.js +15 -0
- package/dist/payment402.js.map +1 -0
- package/dist/settlement.d.ts +11 -0
- package/dist/settlement.d.ts.map +1 -0
- package/dist/settlement.js +46 -0
- package/dist/settlement.js.map +1 -0
- package/dist/signing.d.ts +2 -0
- package/dist/signing.d.ts.map +1 -0
- package/dist/signing.js +4 -0
- package/dist/signing.js.map +1 -0
- package/dist/types.d.ts +43 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/wallet.d.ts +5 -0
- package/dist/wallet.d.ts.map +1 -0
- package/dist/wallet.js +21 -0
- package/dist/wallet.js.map +1 -0
- package/package.json +27 -0
- package/src/balance.ts +26 -0
- package/src/errors.ts +20 -0
- package/src/gateway.ts +10 -0
- package/src/index.ts +38 -0
- package/src/payment402.ts +18 -0
- package/src/settlement.ts +68 -0
- package/src/signing.ts +4 -0
- package/src/types.ts +48 -0
- package/src/wallet.ts +24 -0
- package/test/balance.test.ts +49 -0
- package/test/errors.test.ts +58 -0
- package/test/payment402.test.ts +43 -0
- package/test/settlement.test.ts +108 -0
- package/test/wallet.test.ts +58 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
> @crumb/core@0.1.0 test /Users/taylorferran/Desktop/dev/crumb-sdk/packages/core
|
|
4
|
+
> vitest run
|
|
5
|
+
|
|
6
|
+
[?25l
|
|
7
|
+
[1m[46m RUN [49m[22m [36mv4.0.18 [39m[90m/Users/taylorferran/Desktop/dev/crumb-sdk/packages/core[39m
|
|
8
|
+
|
|
9
|
+
[?2026h
|
|
10
|
+
[1m[33m ❯ [39m[22mtest/payment402.test.ts[2m [queued][22m
|
|
11
|
+
|
|
12
|
+
[2m Test Files [22m[1m[32m0 passed[39m[22m[90m (5)[39m
|
|
13
|
+
[2m Tests [22m[1m[32m0 passed[39m[22m[90m (0)[39m
|
|
14
|
+
[2m Start at [22m11:08:53
|
|
15
|
+
[2m Duration [22m101ms
|
|
16
|
+
[?2026l[?2026h[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K [32m✓[39m test/errors.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 2[2mms[22m[39m
|
|
17
|
+
[32m✓[39m test/balance.test.ts [2m([22m[2m3 tests[22m[2m)[22m[32m 2[2mms[22m[39m
|
|
18
|
+
[32m✓[39m test/payment402.test.ts [2m([22m[2m7 tests[22m[2m)[22m[32m 3[2mms[22m[39m
|
|
19
|
+
[32m✓[39m test/settlement.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 4[2mms[22m[39m
|
|
20
|
+
|
|
21
|
+
[1m[33m ❯ [39m[22mtest/wallet.test.ts[2m 1/8[22m
|
|
22
|
+
|
|
23
|
+
[2m Test Files [22m[1m[32m4 passed[39m[22m[90m (5)[39m
|
|
24
|
+
[2m Tests [22m[1m[32m23 passed[39m[22m[90m (30)[39m
|
|
25
|
+
[2m Start at [22m11:08:53
|
|
26
|
+
[2m Duration [22m202ms
|
|
27
|
+
[?2026l[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K[1A[K [32m✓[39m test/wallet.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 24[2mms[22m[39m
|
|
28
|
+
|
|
29
|
+
[2m Test Files [22m [1m[32m5 passed[39m[22m[90m (5)[39m
|
|
30
|
+
[2m Tests [22m [1m[32m30 passed[39m[22m[90m (30)[39m
|
|
31
|
+
[2m Start at [22m 11:08:53
|
|
32
|
+
[2m Duration [22m 237ms[2m (transform 170ms, setup 0ms, import 258ms, tests 35ms, environment 0ms)[22m
|
|
33
|
+
|
|
34
|
+
[?25h
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { SupportedChainName, Balances } from '@circle-fin/x402-batching/client';
|
|
2
|
+
import type { CrumbWallet } from './types.js';
|
|
3
|
+
export declare function getBalances(wallet: CrumbWallet, chain?: SupportedChainName): Promise<Balances>;
|
|
4
|
+
export declare function getBalancesForAddress(wallet: CrumbWallet, address: string, chain?: SupportedChainName): Promise<Balances>;
|
|
5
|
+
//# sourceMappingURL=balance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"balance.d.ts","sourceRoot":"","sources":["../src/balance.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAA;AACpF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE7C,wBAAsB,WAAW,CAC/B,MAAM,EAAE,WAAW,EACnB,KAAK,GAAE,kBAAiC,GACvC,OAAO,CAAC,QAAQ,CAAC,CAMnB;AAED,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,kBAAiC,GACvC,OAAO,CAAC,QAAQ,CAAC,CAMnB"}
|
package/dist/balance.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { GatewayClient } from '@circle-fin/x402-batching/client';
|
|
2
|
+
export async function getBalances(wallet, chain = 'arcTestnet') {
|
|
3
|
+
const client = new GatewayClient({
|
|
4
|
+
chain,
|
|
5
|
+
privateKey: wallet.privateKey,
|
|
6
|
+
});
|
|
7
|
+
return client.getBalances();
|
|
8
|
+
}
|
|
9
|
+
export async function getBalancesForAddress(wallet, address, chain = 'arcTestnet') {
|
|
10
|
+
const client = new GatewayClient({
|
|
11
|
+
chain,
|
|
12
|
+
privateKey: wallet.privateKey,
|
|
13
|
+
});
|
|
14
|
+
return client.getBalances(address);
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=balance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"balance.js","sourceRoot":"","sources":["../src/balance.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAA;AAIhE,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,MAAmB,EACnB,QAA4B,YAAY;IAExC,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC;QAC/B,KAAK;QACL,UAAU,EAAE,MAAM,CAAC,UAA2B;KAC/C,CAAC,CAAA;IACF,OAAO,MAAM,CAAC,WAAW,EAAE,CAAA;AAC7B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,MAAmB,EACnB,OAAe,EACf,QAA4B,YAAY;IAExC,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC;QAC/B,KAAK;QACL,UAAU,EAAE,MAAM,CAAC,UAA2B;KAC/C,CAAC,CAAA;IACF,OAAO,MAAM,CAAC,WAAW,CAAC,OAAwB,CAAC,CAAA;AACrD,CAAC"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type CrumbErrorCode = 'PRICE_EXCEEDED' | 'INSUFFICIENT_BALANCE' | 'VERIFY_FAILED' | 'SETTLE_FAILED' | 'INVALID_402' | 'NOT_402' | 'PAYMENT_REJECTED' | 'WALLET_NOT_FOUND' | 'NETWORK_ERROR';
|
|
2
|
+
export declare class CrumbError extends Error {
|
|
3
|
+
code: CrumbErrorCode;
|
|
4
|
+
detail?: Record<string, any> | undefined;
|
|
5
|
+
constructor(code: CrumbErrorCode, detail?: Record<string, any> | undefined);
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GACtB,gBAAgB,GAChB,sBAAsB,GACtB,eAAe,GACf,eAAe,GACf,aAAa,GACb,SAAS,GACT,kBAAkB,GAClB,kBAAkB,GAClB,eAAe,CAAA;AAEnB,qBAAa,UAAW,SAAQ,KAAK;IAE1B,IAAI,EAAE,cAAc;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;gBAD5B,IAAI,EAAE,cAAc,EACpB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,YAAA;CAKtC"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export class CrumbError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
detail;
|
|
4
|
+
constructor(code, detail) {
|
|
5
|
+
super(`[${code}] ${JSON.stringify(detail ?? {})}`);
|
|
6
|
+
this.code = code;
|
|
7
|
+
this.detail = detail;
|
|
8
|
+
this.name = 'CrumbError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAWA,MAAM,OAAO,UAAW,SAAQ,KAAK;IAE1B;IACA;IAFT,YACS,IAAoB,EACpB,MAA4B;QAEnC,KAAK,CAAC,IAAI,IAAI,KAAK,IAAI,CAAC,SAAS,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC,CAAA;QAH3C,SAAI,GAAJ,IAAI,CAAgB;QACpB,WAAM,GAAN,MAAM,CAAsB;QAGnC,IAAI,CAAC,IAAI,GAAG,YAAY,CAAA;IAC1B,CAAC;CACF"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { GatewayClient } from '@circle-fin/x402-batching/client';
|
|
2
|
+
import type { SupportedChainName, GatewayClientConfig } from '@circle-fin/x402-batching/client';
|
|
3
|
+
export type { SupportedChainName, GatewayClientConfig };
|
|
4
|
+
export { GatewayClient };
|
|
5
|
+
export { CHAIN_CONFIGS, GATEWAY_DOMAINS } from '@circle-fin/x402-batching/client';
|
|
6
|
+
//# sourceMappingURL=gateway.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAA;AAChE,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAA;AAE/F,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,CAAA;AAGvD,OAAO,EAAE,aAAa,EAAE,CAAA;AAGxB,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAA"}
|
package/dist/gateway.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { GatewayClient } from '@circle-fin/x402-batching/client';
|
|
2
|
+
// Re-export GatewayClient so SDK and CLI can use it
|
|
3
|
+
export { GatewayClient };
|
|
4
|
+
// Re-export chain constants
|
|
5
|
+
export { CHAIN_CONFIGS, GATEWAY_DOMAINS } from '@circle-fin/x402-batching/client';
|
|
6
|
+
//# sourceMappingURL=gateway.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway.js","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAA;AAKhE,oDAAoD;AACpD,OAAO,EAAE,aAAa,EAAE,CAAA;AAExB,4BAA4B;AAC5B,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAA"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type { CrumbWallet, SettlementResult, FetchOptions, FetchResult, MiddlewareConfig, SupportedChainName, Balances, PayResult, } from './types.js';
|
|
2
|
+
export { CrumbError } from './errors.js';
|
|
3
|
+
export type { CrumbErrorCode } from './errors.js';
|
|
4
|
+
export { GatewayClient, CHAIN_CONFIGS, GATEWAY_DOMAINS, } from './gateway.js';
|
|
5
|
+
export type { GatewayClientConfig } from './gateway.js';
|
|
6
|
+
export { registerBatchScheme, BatchEvmScheme } from './signing.js';
|
|
7
|
+
export { verifyPayment, settlePayment, verifyAndSettle, BatchFacilitatorClient } from './settlement.js';
|
|
8
|
+
export { getBalances, getBalancesForAddress } from './balance.js';
|
|
9
|
+
export { checkPriceCeiling } from './payment402.js';
|
|
10
|
+
export { createWallet, walletFromPrivateKey, deriveAddress } from './wallet.js';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,kBAAkB,EAClB,QAAQ,EACR,SAAS,GACV,MAAM,YAAY,CAAA;AAGnB,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAGjD,OAAO,EACL,aAAa,EACb,aAAa,EACb,eAAe,GAChB,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAGvD,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAGlE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AAGvG,OAAO,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAGjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAGnD,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Errors
|
|
2
|
+
export { CrumbError } from './errors.js';
|
|
3
|
+
// Gateway Client
|
|
4
|
+
export { GatewayClient, CHAIN_CONFIGS, GATEWAY_DOMAINS, } from './gateway.js';
|
|
5
|
+
// Signing (advanced)
|
|
6
|
+
export { registerBatchScheme, BatchEvmScheme } from './signing.js';
|
|
7
|
+
// Settlement / Facilitator
|
|
8
|
+
export { verifyPayment, settlePayment, verifyAndSettle, BatchFacilitatorClient } from './settlement.js';
|
|
9
|
+
// Balance
|
|
10
|
+
export { getBalances, getBalancesForAddress } from './balance.js';
|
|
11
|
+
// 402 Payment parsing
|
|
12
|
+
export { checkPriceCeiling } from './payment402.js';
|
|
13
|
+
// Wallet
|
|
14
|
+
export { createWallet, walletFromPrivateKey, deriveAddress } from './wallet.js';
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAYA,SAAS;AACT,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAGxC,iBAAiB;AACjB,OAAO,EACL,aAAa,EACb,aAAa,EACb,eAAe,GAChB,MAAM,cAAc,CAAA;AAGrB,qBAAqB;AACrB,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAElE,2BAA2B;AAC3B,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AAEvG,UAAU;AACV,OAAO,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAEjE,sBAAsB;AACtB,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAEnD,SAAS;AACT,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payment402.d.ts","sourceRoot":"","sources":["../src/payment402.ts"],"names":[],"mappings":"AAEA,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,GACjB,IAAI,CAYN"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CrumbError } from './errors.js';
|
|
2
|
+
export function checkPriceCeiling(amount, maxPayment) {
|
|
3
|
+
// Strip $ prefix if present
|
|
4
|
+
const clean = amount.startsWith('$') ? amount.slice(1) : amount;
|
|
5
|
+
const quoted = parseFloat(clean);
|
|
6
|
+
if (isNaN(quoted))
|
|
7
|
+
return;
|
|
8
|
+
if (quoted > maxPayment) {
|
|
9
|
+
throw new CrumbError('PRICE_EXCEEDED', {
|
|
10
|
+
quoted,
|
|
11
|
+
ceiling: maxPayment,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=payment402.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payment402.js","sourceRoot":"","sources":["../src/payment402.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAExC,MAAM,UAAU,iBAAiB,CAC/B,MAAc,EACd,UAAkB;IAElB,4BAA4B;IAC5B,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;IAC/D,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,CAAA;IAChC,IAAI,KAAK,CAAC,MAAM,CAAC;QAAE,OAAM;IAEzB,IAAI,MAAM,GAAG,UAAU,EAAE,CAAC;QACxB,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE;YACrC,MAAM;YACN,OAAO,EAAE,UAAU;SACpB,CAAC,CAAA;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BatchFacilitatorClient } from '@circle-fin/x402-batching/server';
|
|
2
|
+
import type { SettlementResult } from './types.js';
|
|
3
|
+
export declare function verifyPayment(payload: any, requirements: any, facilitatorUrl?: string): Promise<{
|
|
4
|
+
isValid: boolean;
|
|
5
|
+
invalidReason?: string;
|
|
6
|
+
payer?: string;
|
|
7
|
+
}>;
|
|
8
|
+
export declare function settlePayment(payload: any, requirements: any, facilitatorUrl?: string): Promise<SettlementResult>;
|
|
9
|
+
export declare function verifyAndSettle(payload: any, requirements: any, facilitatorUrl?: string): Promise<SettlementResult>;
|
|
10
|
+
export { BatchFacilitatorClient };
|
|
11
|
+
//# sourceMappingURL=settlement.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settlement.d.ts","sourceRoot":"","sources":["../src/settlement.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAA;AACzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAelD,wBAAsB,aAAa,CACjC,OAAO,EAAE,GAAG,EACZ,YAAY,EAAE,GAAG,EACjB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAWvE;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,GAAG,EACZ,YAAY,EAAE,GAAG,EACjB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,gBAAgB,CAAC,CAmB3B;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,GAAG,EACZ,YAAY,EAAE,GAAG,EACjB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,gBAAgB,CAAC,CAG3B;AAED,OAAO,EAAE,sBAAsB,EAAE,CAAA"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { BatchFacilitatorClient } from '@circle-fin/x402-batching/server';
|
|
2
|
+
import { CrumbError } from './errors.js';
|
|
3
|
+
let defaultFacilitator = null;
|
|
4
|
+
function getFacilitator(facilitatorUrl) {
|
|
5
|
+
if (facilitatorUrl) {
|
|
6
|
+
return new BatchFacilitatorClient({ url: facilitatorUrl });
|
|
7
|
+
}
|
|
8
|
+
if (!defaultFacilitator) {
|
|
9
|
+
defaultFacilitator = new BatchFacilitatorClient();
|
|
10
|
+
}
|
|
11
|
+
return defaultFacilitator;
|
|
12
|
+
}
|
|
13
|
+
export async function verifyPayment(payload, requirements, facilitatorUrl) {
|
|
14
|
+
const facilitator = getFacilitator(facilitatorUrl);
|
|
15
|
+
const result = await facilitator.verify(payload, requirements);
|
|
16
|
+
if (!result.isValid) {
|
|
17
|
+
throw new CrumbError('VERIFY_FAILED', {
|
|
18
|
+
reason: result.invalidReason,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
export async function settlePayment(payload, requirements, facilitatorUrl) {
|
|
24
|
+
const facilitator = getFacilitator(facilitatorUrl);
|
|
25
|
+
const result = await facilitator.settle(payload, requirements);
|
|
26
|
+
if (!result.success) {
|
|
27
|
+
throw new CrumbError('SETTLE_FAILED', {
|
|
28
|
+
errorReason: result.errorReason,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
success: true,
|
|
33
|
+
txHash: result.transaction,
|
|
34
|
+
amount: requirements.amount ?? '0',
|
|
35
|
+
formattedAmount: requirements.amount ?? '0',
|
|
36
|
+
network: result.network,
|
|
37
|
+
payer: result.payer,
|
|
38
|
+
timestamp: new Date().toISOString(),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export async function verifyAndSettle(payload, requirements, facilitatorUrl) {
|
|
42
|
+
await verifyPayment(payload, requirements, facilitatorUrl);
|
|
43
|
+
return settlePayment(payload, requirements, facilitatorUrl);
|
|
44
|
+
}
|
|
45
|
+
export { BatchFacilitatorClient };
|
|
46
|
+
//# sourceMappingURL=settlement.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settlement.js","sourceRoot":"","sources":["../src/settlement.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAA;AAEzE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAExC,IAAI,kBAAkB,GAAkC,IAAI,CAAA;AAE5D,SAAS,cAAc,CAAC,cAAuB;IAC7C,IAAI,cAAc,EAAE,CAAC;QACnB,OAAO,IAAI,sBAAsB,CAAC,EAAE,GAAG,EAAE,cAAc,EAAE,CAAC,CAAA;IAC5D,CAAC;IACD,IAAI,CAAC,kBAAkB,EAAE,CAAC;QACxB,kBAAkB,GAAG,IAAI,sBAAsB,EAAE,CAAA;IACnD,CAAC;IACD,OAAO,kBAAkB,CAAA;AAC3B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAAY,EACZ,YAAiB,EACjB,cAAuB;IAEvB,MAAM,WAAW,GAAG,cAAc,CAAC,cAAc,CAAC,CAAA;IAClD,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,MAAM,CAAC,OAAO,EAAE,YAAY,CAAC,CAAA;IAE9D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,UAAU,CAAC,eAAe,EAAE;YACpC,MAAM,EAAE,MAAM,CAAC,aAAa;SAC7B,CAAC,CAAA;IACJ,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAAY,EACZ,YAAiB,EACjB,cAAuB;IAEvB,MAAM,WAAW,GAAG,cAAc,CAAC,cAAc,CAAC,CAAA;IAClD,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,MAAM,CAAC,OAAO,EAAE,YAAY,CAAC,CAAA;IAE9D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,UAAU,CAAC,eAAe,EAAE;YACpC,WAAW,EAAE,MAAM,CAAC,WAAW;SAChC,CAAC,CAAA;IACJ,CAAC;IAED,OAAO;QACL,OAAO,EAAE,IAAI;QACb,MAAM,EAAE,MAAM,CAAC,WAAW;QAC1B,MAAM,EAAE,YAAY,CAAC,MAAM,IAAI,GAAG;QAClC,eAAe,EAAE,YAAY,CAAC,MAAM,IAAI,GAAG;QAC3C,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAA;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAAY,EACZ,YAAiB,EACjB,cAAuB;IAEvB,MAAM,aAAa,CAAC,OAAO,EAAE,YAAY,EAAE,cAAc,CAAC,CAAA;IAC1D,OAAO,aAAa,CAAC,OAAO,EAAE,YAAY,EAAE,cAAc,CAAC,CAAA;AAC7D,CAAC;AAED,OAAO,EAAE,sBAAsB,EAAE,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signing.d.ts","sourceRoot":"","sources":["../src/signing.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAA"}
|
package/dist/signing.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signing.js","sourceRoot":"","sources":["../src/signing.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,yEAAyE;AAEzE,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAA"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { SupportedChainName, Balances, PayResult } from '@circle-fin/x402-batching/client';
|
|
2
|
+
export type { SupportedChainName, Balances, PayResult };
|
|
3
|
+
export interface CrumbWallet {
|
|
4
|
+
address: string;
|
|
5
|
+
privateKey: string;
|
|
6
|
+
}
|
|
7
|
+
export interface SettlementResult {
|
|
8
|
+
success: boolean;
|
|
9
|
+
txHash: string;
|
|
10
|
+
amount: string;
|
|
11
|
+
formattedAmount: string;
|
|
12
|
+
network: string;
|
|
13
|
+
payer?: string;
|
|
14
|
+
timestamp: string;
|
|
15
|
+
}
|
|
16
|
+
export interface FetchOptions {
|
|
17
|
+
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
18
|
+
body?: unknown;
|
|
19
|
+
headers?: Record<string, string>;
|
|
20
|
+
maxPayment?: number;
|
|
21
|
+
metadata?: Record<string, string>;
|
|
22
|
+
}
|
|
23
|
+
export interface FetchResult<T = unknown> {
|
|
24
|
+
data: T;
|
|
25
|
+
amount: bigint;
|
|
26
|
+
formattedAmount: string;
|
|
27
|
+
txHash: string;
|
|
28
|
+
status: number;
|
|
29
|
+
}
|
|
30
|
+
export interface MiddlewareConfig {
|
|
31
|
+
sellerAddress: string;
|
|
32
|
+
networks?: string | string[];
|
|
33
|
+
facilitatorUrl?: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
onPayment?: (payment: {
|
|
36
|
+
verified: boolean;
|
|
37
|
+
payer: string;
|
|
38
|
+
amount: string;
|
|
39
|
+
network: string;
|
|
40
|
+
transaction?: string;
|
|
41
|
+
}) => void;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAA;AAE/F,YAAY,EAAE,kBAAkB,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAA;AAEvD,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,eAAe,EAAE,MAAM,CAAA;IACvB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAA;IAC1C,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAClC;AAED,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,OAAO;IACtC,IAAI,EAAE,CAAC,CAAA;IACP,MAAM,EAAE,MAAM,CAAA;IACd,eAAe,EAAE,MAAM,CAAA;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE;QACpB,QAAQ,EAAE,OAAO,CAAA;QACjB,KAAK,EAAE,MAAM,CAAA;QACb,MAAM,EAAE,MAAM,CAAA;QACd,OAAO,EAAE,MAAM,CAAA;QACf,WAAW,CAAC,EAAE,MAAM,CAAA;KACrB,KAAK,IAAI,CAAA;CACX"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/dist/wallet.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CrumbWallet } from './types.js';
|
|
2
|
+
export declare function createWallet(): CrumbWallet;
|
|
3
|
+
export declare function walletFromPrivateKey(privateKey: string): CrumbWallet;
|
|
4
|
+
export declare function deriveAddress(privateKey: string): string;
|
|
5
|
+
//# sourceMappingURL=wallet.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wallet.d.ts","sourceRoot":"","sources":["../src/wallet.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE7C,wBAAgB,YAAY,IAAI,WAAW,CAO1C;AAED,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,WAAW,CAMpE;AAED,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAGxD"}
|
package/dist/wallet.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { privateKeyToAccount, generatePrivateKey } from 'viem/accounts';
|
|
2
|
+
export function createWallet() {
|
|
3
|
+
const privateKey = generatePrivateKey();
|
|
4
|
+
const account = privateKeyToAccount(privateKey);
|
|
5
|
+
return {
|
|
6
|
+
address: account.address,
|
|
7
|
+
privateKey,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export function walletFromPrivateKey(privateKey) {
|
|
11
|
+
const account = privateKeyToAccount(privateKey);
|
|
12
|
+
return {
|
|
13
|
+
address: account.address,
|
|
14
|
+
privateKey,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function deriveAddress(privateKey) {
|
|
18
|
+
const account = privateKeyToAccount(privateKey);
|
|
19
|
+
return account.address;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=wallet.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wallet.js","sourceRoot":"","sources":["../src/wallet.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAGvE,MAAM,UAAU,YAAY;IAC1B,MAAM,UAAU,GAAG,kBAAkB,EAAE,CAAA;IACvC,MAAM,OAAO,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAA;IAC/C,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,UAAU;KACX,CAAA;AACH,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,UAAkB;IACrD,MAAM,OAAO,GAAG,mBAAmB,CAAC,UAA2B,CAAC,CAAA;IAChE,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,UAAU;KACX,CAAA;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,UAAkB;IAC9C,MAAM,OAAO,GAAG,mBAAmB,CAAC,UAA2B,CAAC,CAAA;IAChE,OAAO,OAAO,CAAC,OAAO,CAAA;AACxB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "crumb-alpha-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@circle-fin/x402-batching": "^2.0.4",
|
|
19
|
+
"viem": "^2.23.0"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc",
|
|
23
|
+
"dev": "tsc --watch",
|
|
24
|
+
"clean": "rm -rf dist",
|
|
25
|
+
"test": "vitest run"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/balance.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { GatewayClient } from '@circle-fin/x402-batching/client'
|
|
2
|
+
import type { SupportedChainName, Balances } from '@circle-fin/x402-batching/client'
|
|
3
|
+
import type { CrumbWallet } from './types.js'
|
|
4
|
+
|
|
5
|
+
export async function getBalances(
|
|
6
|
+
wallet: CrumbWallet,
|
|
7
|
+
chain: SupportedChainName = 'arcTestnet',
|
|
8
|
+
): Promise<Balances> {
|
|
9
|
+
const client = new GatewayClient({
|
|
10
|
+
chain,
|
|
11
|
+
privateKey: wallet.privateKey as `0x${string}`,
|
|
12
|
+
})
|
|
13
|
+
return client.getBalances()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function getBalancesForAddress(
|
|
17
|
+
wallet: CrumbWallet,
|
|
18
|
+
address: string,
|
|
19
|
+
chain: SupportedChainName = 'arcTestnet',
|
|
20
|
+
): Promise<Balances> {
|
|
21
|
+
const client = new GatewayClient({
|
|
22
|
+
chain,
|
|
23
|
+
privateKey: wallet.privateKey as `0x${string}`,
|
|
24
|
+
})
|
|
25
|
+
return client.getBalances(address as `0x${string}`)
|
|
26
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type CrumbErrorCode =
|
|
2
|
+
| 'PRICE_EXCEEDED' // quoted price > maxPayment ceiling
|
|
3
|
+
| 'INSUFFICIENT_BALANCE' // not enough funds
|
|
4
|
+
| 'VERIFY_FAILED' // facilitator.verify() rejected the payload
|
|
5
|
+
| 'SETTLE_FAILED' // facilitator.settle() returned failure
|
|
6
|
+
| 'INVALID_402' // provider returned malformed 402 payload
|
|
7
|
+
| 'NOT_402' // expected 402, got something else
|
|
8
|
+
| 'PAYMENT_REJECTED' // provider rejected payment header on retry
|
|
9
|
+
| 'WALLET_NOT_FOUND' // no wallet configured
|
|
10
|
+
| 'NETWORK_ERROR' // connectivity issue
|
|
11
|
+
|
|
12
|
+
export class CrumbError extends Error {
|
|
13
|
+
constructor(
|
|
14
|
+
public code: CrumbErrorCode,
|
|
15
|
+
public detail?: Record<string, any>,
|
|
16
|
+
) {
|
|
17
|
+
super(`[${code}] ${JSON.stringify(detail ?? {})}`)
|
|
18
|
+
this.name = 'CrumbError'
|
|
19
|
+
}
|
|
20
|
+
}
|
package/src/gateway.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { GatewayClient } from '@circle-fin/x402-batching/client'
|
|
2
|
+
import type { SupportedChainName, GatewayClientConfig } from '@circle-fin/x402-batching/client'
|
|
3
|
+
|
|
4
|
+
export type { SupportedChainName, GatewayClientConfig }
|
|
5
|
+
|
|
6
|
+
// Re-export GatewayClient so SDK and CLI can use it
|
|
7
|
+
export { GatewayClient }
|
|
8
|
+
|
|
9
|
+
// Re-export chain constants
|
|
10
|
+
export { CHAIN_CONFIGS, GATEWAY_DOMAINS } from '@circle-fin/x402-batching/client'
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Types
|
|
2
|
+
export type {
|
|
3
|
+
CrumbWallet,
|
|
4
|
+
SettlementResult,
|
|
5
|
+
FetchOptions,
|
|
6
|
+
FetchResult,
|
|
7
|
+
MiddlewareConfig,
|
|
8
|
+
SupportedChainName,
|
|
9
|
+
Balances,
|
|
10
|
+
PayResult,
|
|
11
|
+
} from './types.js'
|
|
12
|
+
|
|
13
|
+
// Errors
|
|
14
|
+
export { CrumbError } from './errors.js'
|
|
15
|
+
export type { CrumbErrorCode } from './errors.js'
|
|
16
|
+
|
|
17
|
+
// Gateway Client
|
|
18
|
+
export {
|
|
19
|
+
GatewayClient,
|
|
20
|
+
CHAIN_CONFIGS,
|
|
21
|
+
GATEWAY_DOMAINS,
|
|
22
|
+
} from './gateway.js'
|
|
23
|
+
export type { GatewayClientConfig } from './gateway.js'
|
|
24
|
+
|
|
25
|
+
// Signing (advanced)
|
|
26
|
+
export { registerBatchScheme, BatchEvmScheme } from './signing.js'
|
|
27
|
+
|
|
28
|
+
// Settlement / Facilitator
|
|
29
|
+
export { verifyPayment, settlePayment, verifyAndSettle, BatchFacilitatorClient } from './settlement.js'
|
|
30
|
+
|
|
31
|
+
// Balance
|
|
32
|
+
export { getBalances, getBalancesForAddress } from './balance.js'
|
|
33
|
+
|
|
34
|
+
// 402 Payment parsing
|
|
35
|
+
export { checkPriceCeiling } from './payment402.js'
|
|
36
|
+
|
|
37
|
+
// Wallet
|
|
38
|
+
export { createWallet, walletFromPrivateKey, deriveAddress } from './wallet.js'
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { CrumbError } from './errors.js'
|
|
2
|
+
|
|
3
|
+
export function checkPriceCeiling(
|
|
4
|
+
amount: string,
|
|
5
|
+
maxPayment: number,
|
|
6
|
+
): void {
|
|
7
|
+
// Strip $ prefix if present
|
|
8
|
+
const clean = amount.startsWith('$') ? amount.slice(1) : amount
|
|
9
|
+
const quoted = parseFloat(clean)
|
|
10
|
+
if (isNaN(quoted)) return
|
|
11
|
+
|
|
12
|
+
if (quoted > maxPayment) {
|
|
13
|
+
throw new CrumbError('PRICE_EXCEEDED', {
|
|
14
|
+
quoted,
|
|
15
|
+
ceiling: maxPayment,
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { BatchFacilitatorClient } from '@circle-fin/x402-batching/server'
|
|
2
|
+
import type { SettlementResult } from './types.js'
|
|
3
|
+
import { CrumbError } from './errors.js'
|
|
4
|
+
|
|
5
|
+
let defaultFacilitator: BatchFacilitatorClient | null = null
|
|
6
|
+
|
|
7
|
+
function getFacilitator(facilitatorUrl?: string): BatchFacilitatorClient {
|
|
8
|
+
if (facilitatorUrl) {
|
|
9
|
+
return new BatchFacilitatorClient({ url: facilitatorUrl })
|
|
10
|
+
}
|
|
11
|
+
if (!defaultFacilitator) {
|
|
12
|
+
defaultFacilitator = new BatchFacilitatorClient()
|
|
13
|
+
}
|
|
14
|
+
return defaultFacilitator
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function verifyPayment(
|
|
18
|
+
payload: any,
|
|
19
|
+
requirements: any,
|
|
20
|
+
facilitatorUrl?: string,
|
|
21
|
+
): Promise<{ isValid: boolean; invalidReason?: string; payer?: string }> {
|
|
22
|
+
const facilitator = getFacilitator(facilitatorUrl)
|
|
23
|
+
const result = await facilitator.verify(payload, requirements)
|
|
24
|
+
|
|
25
|
+
if (!result.isValid) {
|
|
26
|
+
throw new CrumbError('VERIFY_FAILED', {
|
|
27
|
+
reason: result.invalidReason,
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return result
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function settlePayment(
|
|
35
|
+
payload: any,
|
|
36
|
+
requirements: any,
|
|
37
|
+
facilitatorUrl?: string,
|
|
38
|
+
): Promise<SettlementResult> {
|
|
39
|
+
const facilitator = getFacilitator(facilitatorUrl)
|
|
40
|
+
const result = await facilitator.settle(payload, requirements)
|
|
41
|
+
|
|
42
|
+
if (!result.success) {
|
|
43
|
+
throw new CrumbError('SETTLE_FAILED', {
|
|
44
|
+
errorReason: result.errorReason,
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
success: true,
|
|
50
|
+
txHash: result.transaction,
|
|
51
|
+
amount: requirements.amount ?? '0',
|
|
52
|
+
formattedAmount: requirements.amount ?? '0',
|
|
53
|
+
network: result.network,
|
|
54
|
+
payer: result.payer,
|
|
55
|
+
timestamp: new Date().toISOString(),
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function verifyAndSettle(
|
|
60
|
+
payload: any,
|
|
61
|
+
requirements: any,
|
|
62
|
+
facilitatorUrl?: string,
|
|
63
|
+
): Promise<SettlementResult> {
|
|
64
|
+
await verifyPayment(payload, requirements, facilitatorUrl)
|
|
65
|
+
return settlePayment(payload, requirements, facilitatorUrl)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export { BatchFacilitatorClient }
|
package/src/signing.ts
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { SupportedChainName, Balances, PayResult } from '@circle-fin/x402-batching/client'
|
|
2
|
+
|
|
3
|
+
export type { SupportedChainName, Balances, PayResult }
|
|
4
|
+
|
|
5
|
+
export interface CrumbWallet {
|
|
6
|
+
address: string
|
|
7
|
+
privateKey: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface SettlementResult {
|
|
11
|
+
success: boolean
|
|
12
|
+
txHash: string
|
|
13
|
+
amount: string
|
|
14
|
+
formattedAmount: string
|
|
15
|
+
network: string
|
|
16
|
+
payer?: string
|
|
17
|
+
timestamp: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface FetchOptions {
|
|
21
|
+
method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
|
|
22
|
+
body?: unknown
|
|
23
|
+
headers?: Record<string, string>
|
|
24
|
+
maxPayment?: number // USDC ceiling — throws if quoted price exceeds this
|
|
25
|
+
metadata?: Record<string, string>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface FetchResult<T = unknown> {
|
|
29
|
+
data: T
|
|
30
|
+
amount: bigint
|
|
31
|
+
formattedAmount: string
|
|
32
|
+
txHash: string
|
|
33
|
+
status: number
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface MiddlewareConfig {
|
|
37
|
+
sellerAddress: string
|
|
38
|
+
networks?: string | string[]
|
|
39
|
+
facilitatorUrl?: string
|
|
40
|
+
description?: string
|
|
41
|
+
onPayment?: (payment: {
|
|
42
|
+
verified: boolean
|
|
43
|
+
payer: string
|
|
44
|
+
amount: string
|
|
45
|
+
network: string
|
|
46
|
+
transaction?: string
|
|
47
|
+
}) => void
|
|
48
|
+
}
|
package/src/wallet.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { privateKeyToAccount, generatePrivateKey } from 'viem/accounts'
|
|
2
|
+
import type { CrumbWallet } from './types.js'
|
|
3
|
+
|
|
4
|
+
export function createWallet(): CrumbWallet {
|
|
5
|
+
const privateKey = generatePrivateKey()
|
|
6
|
+
const account = privateKeyToAccount(privateKey)
|
|
7
|
+
return {
|
|
8
|
+
address: account.address,
|
|
9
|
+
privateKey,
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function walletFromPrivateKey(privateKey: string): CrumbWallet {
|
|
14
|
+
const account = privateKeyToAccount(privateKey as `0x${string}`)
|
|
15
|
+
return {
|
|
16
|
+
address: account.address,
|
|
17
|
+
privateKey,
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function deriveAddress(privateKey: string): string {
|
|
22
|
+
const account = privateKeyToAccount(privateKey as `0x${string}`)
|
|
23
|
+
return account.address
|
|
24
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
const mockGetBalances = vi.fn()
|
|
4
|
+
|
|
5
|
+
class MockGatewayClient {
|
|
6
|
+
getBalances = mockGetBalances
|
|
7
|
+
constructor(public config: any) {}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
vi.mock('@circle-fin/x402-batching/client', () => ({
|
|
11
|
+
GatewayClient: MockGatewayClient,
|
|
12
|
+
}))
|
|
13
|
+
|
|
14
|
+
const { getBalances, getBalancesForAddress } = await import('../src/balance.js')
|
|
15
|
+
|
|
16
|
+
describe('getBalances', () => {
|
|
17
|
+
it('creates a GatewayClient and calls getBalances', async () => {
|
|
18
|
+
const mockResult = {
|
|
19
|
+
wallet: { formatted: '10.00' },
|
|
20
|
+
gateway: { formattedAvailable: '5.00' },
|
|
21
|
+
}
|
|
22
|
+
mockGetBalances.mockResolvedValue(mockResult)
|
|
23
|
+
|
|
24
|
+
const wallet = { address: '0xabc', privateKey: '0x1234' }
|
|
25
|
+
const result = await getBalances(wallet)
|
|
26
|
+
expect(result).toEqual(mockResult)
|
|
27
|
+
expect(mockGetBalances).toHaveBeenCalled()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('uses baseSepolia as default chain', async () => {
|
|
31
|
+
mockGetBalances.mockResolvedValue({})
|
|
32
|
+
|
|
33
|
+
const wallet = { address: '0xabc', privateKey: '0x1234' }
|
|
34
|
+
await getBalances(wallet)
|
|
35
|
+
|
|
36
|
+
expect(mockGetBalances).toHaveBeenCalled()
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
describe('getBalancesForAddress', () => {
|
|
41
|
+
it('passes address to getBalances call', async () => {
|
|
42
|
+
mockGetBalances.mockResolvedValue({})
|
|
43
|
+
|
|
44
|
+
const wallet = { address: '0xabc', privateKey: '0x1234' }
|
|
45
|
+
await getBalancesForAddress(wallet, '0xother')
|
|
46
|
+
|
|
47
|
+
expect(mockGetBalances).toHaveBeenCalledWith('0xother')
|
|
48
|
+
})
|
|
49
|
+
})
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { CrumbError } from '../src/errors.js'
|
|
3
|
+
|
|
4
|
+
describe('CrumbError', () => {
|
|
5
|
+
it('creates an error with code and detail', () => {
|
|
6
|
+
const err = new CrumbError('PRICE_EXCEEDED', { quoted: 0.5, ceiling: 0.01 })
|
|
7
|
+
expect(err).toBeInstanceOf(Error)
|
|
8
|
+
expect(err).toBeInstanceOf(CrumbError)
|
|
9
|
+
expect(err.code).toBe('PRICE_EXCEEDED')
|
|
10
|
+
expect(err.detail).toEqual({ quoted: 0.5, ceiling: 0.01 })
|
|
11
|
+
expect(err.name).toBe('CrumbError')
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('includes code in message', () => {
|
|
15
|
+
const err = new CrumbError('INSUFFICIENT_BALANCE')
|
|
16
|
+
expect(err.message).toContain('INSUFFICIENT_BALANCE')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('includes detail in message as JSON', () => {
|
|
20
|
+
const err = new CrumbError('VERIFY_FAILED', { reason: 'expired' })
|
|
21
|
+
expect(err.message).toContain('expired')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('works without detail', () => {
|
|
25
|
+
const err = new CrumbError('NETWORK_ERROR')
|
|
26
|
+
expect(err.code).toBe('NETWORK_ERROR')
|
|
27
|
+
expect(err.detail).toBeUndefined()
|
|
28
|
+
expect(err.message).toContain('NETWORK_ERROR')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('can be caught as Error', () => {
|
|
32
|
+
try {
|
|
33
|
+
throw new CrumbError('WALLET_NOT_FOUND')
|
|
34
|
+
} catch (e) {
|
|
35
|
+
expect(e).toBeInstanceOf(Error)
|
|
36
|
+
expect((e as CrumbError).code).toBe('WALLET_NOT_FOUND')
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('preserves all error code types', () => {
|
|
41
|
+
const codes = [
|
|
42
|
+
'PRICE_EXCEEDED',
|
|
43
|
+
'INSUFFICIENT_BALANCE',
|
|
44
|
+
'VERIFY_FAILED',
|
|
45
|
+
'SETTLE_FAILED',
|
|
46
|
+
'INVALID_402',
|
|
47
|
+
'NOT_402',
|
|
48
|
+
'PAYMENT_REJECTED',
|
|
49
|
+
'WALLET_NOT_FOUND',
|
|
50
|
+
'NETWORK_ERROR',
|
|
51
|
+
] as const
|
|
52
|
+
|
|
53
|
+
for (const code of codes) {
|
|
54
|
+
const err = new CrumbError(code)
|
|
55
|
+
expect(err.code).toBe(code)
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { checkPriceCeiling } from '../src/payment402.js'
|
|
3
|
+
import { CrumbError } from '../src/errors.js'
|
|
4
|
+
|
|
5
|
+
describe('checkPriceCeiling', () => {
|
|
6
|
+
it('does not throw when price is under ceiling', () => {
|
|
7
|
+
expect(() => checkPriceCeiling('0.001', 0.01)).not.toThrow()
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('does not throw when price equals ceiling', () => {
|
|
11
|
+
expect(() => checkPriceCeiling('0.01', 0.01)).not.toThrow()
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('throws PRICE_EXCEEDED when price exceeds ceiling', () => {
|
|
15
|
+
expect(() => checkPriceCeiling('0.05', 0.01)).toThrow(CrumbError)
|
|
16
|
+
try {
|
|
17
|
+
checkPriceCeiling('0.05', 0.01)
|
|
18
|
+
} catch (e) {
|
|
19
|
+
expect((e as CrumbError).code).toBe('PRICE_EXCEEDED')
|
|
20
|
+
expect((e as CrumbError).detail).toEqual({ quoted: 0.05, ceiling: 0.01 })
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('handles $ prefix in amount', () => {
|
|
25
|
+
expect(() => checkPriceCeiling('$0.001', 0.01)).not.toThrow()
|
|
26
|
+
expect(() => checkPriceCeiling('$0.05', 0.01)).toThrow(CrumbError)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('does not throw for non-numeric amounts', () => {
|
|
30
|
+
expect(() => checkPriceCeiling('free', 0.01)).not.toThrow()
|
|
31
|
+
expect(() => checkPriceCeiling('', 0.01)).not.toThrow()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('handles large amounts correctly', () => {
|
|
35
|
+
expect(() => checkPriceCeiling('100.00', 50)).toThrow(CrumbError)
|
|
36
|
+
expect(() => checkPriceCeiling('50.00', 100)).not.toThrow()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('handles very small amounts', () => {
|
|
40
|
+
expect(() => checkPriceCeiling('0.0001', 0.001)).not.toThrow()
|
|
41
|
+
expect(() => checkPriceCeiling('0.001', 0.0001)).toThrow(CrumbError)
|
|
42
|
+
})
|
|
43
|
+
})
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
2
|
+
import { CrumbError } from '../src/errors.js'
|
|
3
|
+
|
|
4
|
+
const mockVerify = vi.fn()
|
|
5
|
+
const mockSettle = vi.fn()
|
|
6
|
+
|
|
7
|
+
class MockBatchFacilitatorClient {
|
|
8
|
+
verify = mockVerify
|
|
9
|
+
settle = mockSettle
|
|
10
|
+
constructor(_config?: any) {}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
vi.mock('@circle-fin/x402-batching/server', () => ({
|
|
14
|
+
BatchFacilitatorClient: MockBatchFacilitatorClient,
|
|
15
|
+
}))
|
|
16
|
+
|
|
17
|
+
// Import after mocking
|
|
18
|
+
const { verifyPayment, settlePayment, verifyAndSettle } = await import('../src/settlement.js')
|
|
19
|
+
|
|
20
|
+
describe('verifyPayment', () => {
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
vi.clearAllMocks()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('returns result when verification succeeds', async () => {
|
|
26
|
+
mockVerify.mockResolvedValue({ isValid: true, payer: '0xabc' })
|
|
27
|
+
|
|
28
|
+
const result = await verifyPayment({ payment: 'data' }, { amount: '0.01' })
|
|
29
|
+
expect(result.isValid).toBe(true)
|
|
30
|
+
expect(result.payer).toBe('0xabc')
|
|
31
|
+
expect(mockVerify).toHaveBeenCalledWith({ payment: 'data' }, { amount: '0.01' })
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('throws VERIFY_FAILED when verification fails', async () => {
|
|
35
|
+
mockVerify.mockResolvedValue({ isValid: false, invalidReason: 'expired signature' })
|
|
36
|
+
|
|
37
|
+
await expect(verifyPayment({}, {})).rejects.toThrow(CrumbError)
|
|
38
|
+
try {
|
|
39
|
+
await verifyPayment({}, {})
|
|
40
|
+
} catch (e) {
|
|
41
|
+
expect((e as CrumbError).code).toBe('VERIFY_FAILED')
|
|
42
|
+
expect((e as CrumbError).detail?.reason).toBe('expired signature')
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
describe('settlePayment', () => {
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
vi.clearAllMocks()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('returns SettlementResult on success', async () => {
|
|
53
|
+
mockSettle.mockResolvedValue({
|
|
54
|
+
success: true,
|
|
55
|
+
transaction: '0xtxhash',
|
|
56
|
+
network: 'arcTestnet',
|
|
57
|
+
payer: '0xpayer',
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const result = await settlePayment({}, { amount: '0.001' })
|
|
61
|
+
expect(result.success).toBe(true)
|
|
62
|
+
expect(result.txHash).toBe('0xtxhash')
|
|
63
|
+
expect(result.network).toBe('arcTestnet')
|
|
64
|
+
expect(result.payer).toBe('0xpayer')
|
|
65
|
+
expect(result.amount).toBe('0.001')
|
|
66
|
+
expect(result.timestamp).toBeDefined()
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('throws SETTLE_FAILED when settlement fails', async () => {
|
|
70
|
+
mockSettle.mockResolvedValue({ success: false, errorReason: 'insufficient funds' })
|
|
71
|
+
|
|
72
|
+
await expect(settlePayment({}, {})).rejects.toThrow(CrumbError)
|
|
73
|
+
try {
|
|
74
|
+
await settlePayment({}, {})
|
|
75
|
+
} catch (e) {
|
|
76
|
+
expect((e as CrumbError).code).toBe('SETTLE_FAILED')
|
|
77
|
+
expect((e as CrumbError).detail?.errorReason).toBe('insufficient funds')
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
describe('verifyAndSettle', () => {
|
|
83
|
+
beforeEach(() => {
|
|
84
|
+
vi.clearAllMocks()
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('calls verify then settle', async () => {
|
|
88
|
+
mockVerify.mockResolvedValue({ isValid: true, payer: '0xabc' })
|
|
89
|
+
mockSettle.mockResolvedValue({
|
|
90
|
+
success: true,
|
|
91
|
+
transaction: '0xtx',
|
|
92
|
+
network: 'arcTestnet',
|
|
93
|
+
payer: '0xabc',
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
const result = await verifyAndSettle({}, { amount: '0.01' })
|
|
97
|
+
expect(mockVerify).toHaveBeenCalled()
|
|
98
|
+
expect(mockSettle).toHaveBeenCalled()
|
|
99
|
+
expect(result.success).toBe(true)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('does not settle if verify fails', async () => {
|
|
103
|
+
mockVerify.mockResolvedValue({ isValid: false, invalidReason: 'bad sig' })
|
|
104
|
+
|
|
105
|
+
await expect(verifyAndSettle({}, {})).rejects.toThrow(CrumbError)
|
|
106
|
+
expect(mockSettle).not.toHaveBeenCalled()
|
|
107
|
+
})
|
|
108
|
+
})
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { createWallet, walletFromPrivateKey, deriveAddress } from '../src/wallet.js'
|
|
3
|
+
|
|
4
|
+
describe('createWallet', () => {
|
|
5
|
+
it('returns an object with address and privateKey', () => {
|
|
6
|
+
const wallet = createWallet()
|
|
7
|
+
expect(wallet).toHaveProperty('address')
|
|
8
|
+
expect(wallet).toHaveProperty('privateKey')
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it('generates a valid hex private key', () => {
|
|
12
|
+
const wallet = createWallet()
|
|
13
|
+
expect(wallet.privateKey).toMatch(/^0x[0-9a-f]{64}$/i)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('generates a valid checksummed address', () => {
|
|
17
|
+
const wallet = createWallet()
|
|
18
|
+
expect(wallet.address).toMatch(/^0x[0-9a-fA-F]{40}$/)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('generates unique wallets each time', () => {
|
|
22
|
+
const a = createWallet()
|
|
23
|
+
const b = createWallet()
|
|
24
|
+
expect(a.privateKey).not.toBe(b.privateKey)
|
|
25
|
+
expect(a.address).not.toBe(b.address)
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
describe('walletFromPrivateKey', () => {
|
|
30
|
+
it('derives the correct address from a known private key', () => {
|
|
31
|
+
const pk = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'
|
|
32
|
+
const wallet = walletFromPrivateKey(pk)
|
|
33
|
+
expect(wallet.privateKey).toBe(pk)
|
|
34
|
+
// This is the well-known Hardhat account #0 address
|
|
35
|
+
expect(wallet.address).toBe('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('returns consistent results for the same key', () => {
|
|
39
|
+
const pk = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'
|
|
40
|
+
const a = walletFromPrivateKey(pk)
|
|
41
|
+
const b = walletFromPrivateKey(pk)
|
|
42
|
+
expect(a.address).toBe(b.address)
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
describe('deriveAddress', () => {
|
|
47
|
+
it('derives address from a private key', () => {
|
|
48
|
+
const pk = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'
|
|
49
|
+
const address = deriveAddress(pk)
|
|
50
|
+
expect(address).toBe('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('matches createWallet output', () => {
|
|
54
|
+
const wallet = createWallet()
|
|
55
|
+
const address = deriveAddress(wallet.privateKey)
|
|
56
|
+
expect(address).toBe(wallet.address)
|
|
57
|
+
})
|
|
58
|
+
})
|