@defdone/annon 0.0.0-stage → 0.1.0-alpha.1
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/CHANGELOG.md +63 -0
- package/CLI.md +378 -0
- package/LICENSE +201 -0
- package/NOTICE +7 -0
- package/README.md +591 -2
- package/dist/api/annon-types.d.ts +108 -0
- package/dist/api/annon-types.js +2 -0
- package/dist/api/annon.d.ts +117 -0
- package/dist/api/annon.js +1152 -0
- package/dist/api/hosted-record.d.ts +13 -0
- package/dist/api/hosted-record.js +149 -0
- package/dist/api/sharing-types.d.ts +109 -0
- package/dist/api/sharing-types.js +1 -0
- package/dist/api/sharing.d.ts +36 -0
- package/dist/api/sharing.js +879 -0
- package/dist/browser/download.d.ts +19 -0
- package/dist/browser/download.js +52 -0
- package/dist/browser/index.d.ts +23 -0
- package/dist/browser/index.js +22 -0
- package/dist/browser/login-bundle.js +151 -0
- package/dist/browser/login.d.ts +1 -0
- package/dist/browser/login.js +216 -0
- package/dist/browser/purchase-bundle.js +84 -0
- package/dist/browser/purchases.d.ts +98 -0
- package/dist/browser/purchases.js +735 -0
- package/dist/browser/record.d.ts +55 -0
- package/dist/browser/record.js +16 -0
- package/dist/browser/selling.d.ts +146 -0
- package/dist/browser/selling.js +418 -0
- package/dist/browser/wallets.d.ts +53 -0
- package/dist/browser/wallets.js +286 -0
- package/dist/bytes.d.ts +4 -0
- package/dist/bytes.js +16 -0
- package/dist/download/download.d.ts +59 -0
- package/dist/download/download.js +384 -0
- package/dist/download/host.d.ts +56 -0
- package/dist/download/host.js +161 -0
- package/dist/download/http.d.ts +23 -0
- package/dist/download/http.js +87 -0
- package/dist/download/proof.d.ts +20 -0
- package/dist/download/proof.js +79 -0
- package/dist/download/upload.d.ts +130 -0
- package/dist/download/upload.js +408 -0
- package/dist/envelope/evm.d.ts +4 -0
- package/dist/envelope/evm.js +32 -0
- package/dist/envelope/pay2.d.ts +176 -0
- package/dist/envelope/pay2.js +541 -0
- package/dist/envelope/replay.d.ts +45 -0
- package/dist/envelope/replay.js +186 -0
- package/dist/envelope/stream.d.ts +78 -0
- package/dist/envelope/stream.js +372 -0
- package/dist/errors.d.ts +21 -0
- package/dist/errors.js +300 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +23 -0
- package/dist/keys/near-ckd.d.ts +47 -0
- package/dist/keys/near-ckd.js +96 -0
- package/dist/limits.d.ts +4 -0
- package/dist/limits.js +4 -0
- package/dist/near/nep413.d.ts +22 -0
- package/dist/near/nep413.js +39 -0
- package/dist/near/rpc.d.ts +170 -0
- package/dist/near/rpc.js +423 -0
- package/dist/near/texts.d.ts +39 -0
- package/dist/near/texts.js +60 -0
- package/dist/net/ip.d.ts +7 -0
- package/dist/net/ip.js +62 -0
- package/dist/net/retry.d.ts +3 -0
- package/dist/net/retry.js +31 -0
- package/dist/node/adapter-checks.d.ts +6 -0
- package/dist/node/adapter-checks.js +13 -0
- package/dist/node/adapter.d.ts +8 -0
- package/dist/node/adapter.js +98 -0
- package/dist/node/agent.d.ts +49 -0
- package/dist/node/agent.js +266 -0
- package/dist/node/amounts.d.ts +2 -0
- package/dist/node/amounts.js +20 -0
- package/dist/node/browser-login.d.ts +25 -0
- package/dist/node/browser-login.js +219 -0
- package/dist/node/budget.d.ts +24 -0
- package/dist/node/budget.js +59 -0
- package/dist/node/buy-commands.d.ts +24 -0
- package/dist/node/buy-commands.js +301 -0
- package/dist/node/cli.d.ts +60 -0
- package/dist/node/cli.js +201 -0
- package/dist/node/command-context.d.ts +82 -0
- package/dist/node/command-context.js +77 -0
- package/dist/node/config.d.ts +37 -0
- package/dist/node/config.js +189 -0
- package/dist/node/core-adapter.d.ts +43 -0
- package/dist/node/core-adapter.js +122 -0
- package/dist/node/errors.d.ts +8 -0
- package/dist/node/errors.js +12 -0
- package/dist/node/index.d.ts +7 -0
- package/dist/node/index.js +5 -0
- package/dist/node/io.d.ts +13 -0
- package/dist/node/io.js +112 -0
- package/dist/node/keychain.d.ts +10 -0
- package/dist/node/keychain.js +67 -0
- package/dist/node/main.d.ts +2 -0
- package/dist/node/main.js +21 -0
- package/dist/node/offers.d.ts +16 -0
- package/dist/node/offers.js +91 -0
- package/dist/node/origin.d.ts +11 -0
- package/dist/node/origin.js +80 -0
- package/dist/node/paths.d.ts +3 -0
- package/dist/node/paths.js +71 -0
- package/dist/node/payment-terms.d.ts +26 -0
- package/dist/node/payment-terms.js +127 -0
- package/dist/node/purchase-commands.d.ts +16 -0
- package/dist/node/purchase-commands.js +237 -0
- package/dist/node/purchase-guard.d.ts +19 -0
- package/dist/node/purchase-guard.js +53 -0
- package/dist/node/relay-transport.d.ts +6 -0
- package/dist/node/relay-transport.js +173 -0
- package/dist/node/resources.d.ts +2 -0
- package/dist/node/resources.js +26 -0
- package/dist/node/retry-gate.d.ts +15 -0
- package/dist/node/retry-gate.js +53 -0
- package/dist/node/seller-commands.d.ts +12 -0
- package/dist/node/seller-commands.js +207 -0
- package/dist/node/sharing.d.ts +3 -0
- package/dist/node/sharing.js +28 -0
- package/dist/node/shops.d.ts +17 -0
- package/dist/node/shops.js +74 -0
- package/dist/node/signer-mock.d.ts +11 -0
- package/dist/node/signer-mock.js +79 -0
- package/dist/node/signer-wallet.d.ts +8 -0
- package/dist/node/signer-wallet.js +149 -0
- package/dist/node/signing.d.ts +29 -0
- package/dist/node/signing.js +200 -0
- package/dist/node/storage-commands.d.ts +11 -0
- package/dist/node/storage-commands.js +50 -0
- package/dist/node/sync-dir.d.ts +1 -0
- package/dist/node/sync-dir.js +11 -0
- package/dist/node/wallets.d.ts +13 -0
- package/dist/node/wallets.js +146 -0
- package/dist/oneclick/oneclick.d.ts +132 -0
- package/dist/oneclick/oneclick.js +342 -0
- package/dist/pay/backup.d.ts +70 -0
- package/dist/pay/backup.js +402 -0
- package/dist/pay/base.d.ts +135 -0
- package/dist/pay/base.js +386 -0
- package/dist/pay/eip3009.d.ts +59 -0
- package/dist/pay/eip3009.js +72 -0
- package/dist/pay/pay.d.ts +86 -0
- package/dist/pay/pay.js +297 -0
- package/dist/pay/purchase.d.ts +103 -0
- package/dist/pay/purchase.js +20 -0
- package/dist/pay/recover.d.ts +31 -0
- package/dist/pay/recover.js +34 -0
- package/dist/pay/seller.d.ts +130 -0
- package/dist/pay/seller.js +444 -0
- package/dist/pay/settle.d.ts +96 -0
- package/dist/pay/settle.js +308 -0
- package/dist/pay/split.d.ts +115 -0
- package/dist/pay/split.js +256 -0
- package/dist/pay/store-node.d.ts +8 -0
- package/dist/pay/store-node.js +224 -0
- package/dist/pay/store.d.ts +54 -0
- package/dist/pay/store.js +135 -0
- package/dist/pay/x402.d.ts +95 -0
- package/dist/pay/x402.js +136 -0
- package/dist/relay/admission.d.ts +8 -0
- package/dist/relay/admission.js +58 -0
- package/dist/relay/capabilities.d.ts +36 -0
- package/dist/relay/capabilities.js +232 -0
- package/dist/relay/fees.d.ts +17 -0
- package/dist/relay/fees.js +105 -0
- package/dist/relay/index.d.ts +10 -0
- package/dist/relay/index.js +8 -0
- package/dist/relay/keeper-balance.d.ts +17 -0
- package/dist/relay/keeper-balance.js +46 -0
- package/dist/relay/keeper-runner.d.ts +114 -0
- package/dist/relay/keeper-runner.js +703 -0
- package/dist/relay/keeper.d.ts +38 -0
- package/dist/relay/keeper.js +73 -0
- package/dist/relay/ledger.d.ts +155 -0
- package/dist/relay/ledger.js +392 -0
- package/dist/relay/relay-checks.d.ts +3 -0
- package/dist/relay/relay-checks.js +244 -0
- package/dist/relay/relay-reconciliation.d.ts +15 -0
- package/dist/relay/relay-reconciliation.js +293 -0
- package/dist/relay/relay-request.d.ts +6 -0
- package/dist/relay/relay-request.js +139 -0
- package/dist/relay/relay-transactions.d.ts +23 -0
- package/dist/relay/relay-transactions.js +182 -0
- package/dist/relay/relay-types.d.ts +128 -0
- package/dist/relay/relay-types.js +17 -0
- package/dist/relay/relay.d.ts +102 -0
- package/dist/relay/relay.js +426 -0
- package/dist/relay/reservation.d.ts +29 -0
- package/dist/relay/reservation.js +132 -0
- package/dist/relay/sponsorship.d.ts +42 -0
- package/dist/relay/sponsorship.js +184 -0
- package/dist/routes.d.ts +2 -0
- package/dist/routes.js +1 -0
- package/dist/settle-near/codec.d.ts +17 -0
- package/dist/settle-near/codec.js +86 -0
- package/dist/settle-near/identity.d.ts +27 -0
- package/dist/settle-near/identity.js +80 -0
- package/dist/settle-near/purchase.d.ts +40 -0
- package/dist/settle-near/purchase.js +153 -0
- package/dist/settle-near/terms.d.ts +37 -0
- package/dist/settle-near/terms.js +89 -0
- package/dist/shops/discovery.d.ts +95 -0
- package/dist/shops/discovery.js +384 -0
- package/dist/wallet/account-only.d.ts +11 -0
- package/dist/wallet/account-only.js +6 -0
- package/dist/wallet/uncertain.d.ts +8 -0
- package/dist/wallet/uncertain.js +13 -0
- package/dist/wallet/wallet.d.ts +100 -0
- package/dist/wallet/wallet.js +204 -0
- package/package.json +136 -3
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/** Portable purchase backups and encrypted CAS storage. The 32-byte secret is user-held randomness. */
|
|
2
|
+
import { type HostedCheckpoint } from '../api/sharing.js';
|
|
3
|
+
import { type BrowserPurchaseIndex, type BrowserReceipt, type BrowserRecoveryArchiveIndex, type StoredBrowserPurchase } from '../browser/record.js';
|
|
4
|
+
import type { PurchaseRecord } from './purchase.js';
|
|
5
|
+
import { type Store, type Versioned } from './store.js';
|
|
6
|
+
export interface RecoveryRecord extends Versioned {
|
|
7
|
+
kind: 'recovery';
|
|
8
|
+
purchase_id: string;
|
|
9
|
+
file_id: string;
|
|
10
|
+
recovery: import('../settle-near/identity.js').Identity;
|
|
11
|
+
buyerSecret: string;
|
|
12
|
+
fresh_key: string;
|
|
13
|
+
nonce: string;
|
|
14
|
+
deadline: number;
|
|
15
|
+
grant: number | null;
|
|
16
|
+
}
|
|
17
|
+
export interface SellRecord extends Versioned {
|
|
18
|
+
hosting?: HostedCheckpoint;
|
|
19
|
+
storageBudget?: string;
|
|
20
|
+
storageFunding?: 'pending' | 'funded' | 'refused';
|
|
21
|
+
kind: 'sell';
|
|
22
|
+
terms: import('../settle-near/terms.js').Terms;
|
|
23
|
+
/** Immutable Base split provenance. Recipients and bps come from terms.near;
|
|
24
|
+
* totalAllocation is 10000 and distributionIncentive is 0. Fixed before registration. */
|
|
25
|
+
baseSplit: {
|
|
26
|
+
address: string;
|
|
27
|
+
kind: 'push' | 'pull';
|
|
28
|
+
salt: string;
|
|
29
|
+
} | null;
|
|
30
|
+
published?: boolean;
|
|
31
|
+
fileKey: string | null;
|
|
32
|
+
sellerSecret: string | null;
|
|
33
|
+
deadline: number;
|
|
34
|
+
name: string;
|
|
35
|
+
mime: string;
|
|
36
|
+
previewLen: number;
|
|
37
|
+
ckdSecret: string | null;
|
|
38
|
+
}
|
|
39
|
+
export interface SellerKeyRecord extends Versioned {
|
|
40
|
+
kind: 'seller-key';
|
|
41
|
+
file_id: string;
|
|
42
|
+
seller: import('../settle-near/identity.js').Identity;
|
|
43
|
+
buyerSecret: string | null;
|
|
44
|
+
deadline: number;
|
|
45
|
+
}
|
|
46
|
+
export interface EncryptedRecord extends Versioned {
|
|
47
|
+
backup: string;
|
|
48
|
+
}
|
|
49
|
+
export declare const newBackupSecret: () => Uint8Array<ArrayBuffer>;
|
|
50
|
+
export declare function validatePurchaseRecord(r: PurchaseRecord): void;
|
|
51
|
+
export declare const exportPurchase: (record: PurchaseRecord, secret: Uint8Array) => Promise<string>;
|
|
52
|
+
export declare const importPurchase: (file: string, secret: Uint8Array) => Promise<PurchaseRecord>;
|
|
53
|
+
export declare const exportRecovery: (record: RecoveryRecord, secret: Uint8Array) => Promise<string>;
|
|
54
|
+
export declare const importRecovery: (file: string, secret: Uint8Array) => Promise<RecoveryRecord>;
|
|
55
|
+
export declare const encryptedPurchaseStore: (raw: Store<EncryptedRecord>, secret: Uint8Array) => Store<PurchaseRecord>;
|
|
56
|
+
export declare const encryptedRecoveryStore: (raw: Store<EncryptedRecord>, secret: Uint8Array) => Store<RecoveryRecord>;
|
|
57
|
+
export declare const exportSell: (record: SellRecord, secret: Uint8Array) => Promise<string>;
|
|
58
|
+
export declare const importSell: (file: string, secret: Uint8Array) => Promise<SellRecord>;
|
|
59
|
+
export declare const encryptedSellStore: (raw: Store<EncryptedRecord>, secret: Uint8Array) => Store<SellRecord>;
|
|
60
|
+
export declare const exportSellerKey: (record: SellerKeyRecord, secret: Uint8Array) => Promise<string>;
|
|
61
|
+
export declare function importSellerKey(file: string, secret: Uint8Array): Promise<SellerKeyRecord>;
|
|
62
|
+
export declare const encryptedSellerKeyStore: (raw: Store<EncryptedRecord>, secret: Uint8Array) => Store<SellerKeyRecord>;
|
|
63
|
+
/** Browser workflow records use the same authenticated encryption and CAS binding as purchases. */
|
|
64
|
+
export declare const exportBrowserReceipt: (record: BrowserReceipt, secret: Uint8Array) => Promise<string>;
|
|
65
|
+
export declare function importBrowserReceipt(file: string, secret: Uint8Array): Promise<BrowserReceipt>;
|
|
66
|
+
export declare const encryptedBrowserIndexStore: (raw: Store<EncryptedRecord>, secret: Uint8Array) => Store<BrowserPurchaseIndex>;
|
|
67
|
+
export declare const encryptedBrowserPurchaseStore: (raw: Store<EncryptedRecord>, secret: Uint8Array) => Store<StoredBrowserPurchase>;
|
|
68
|
+
/** Prepare ciphertext outside an IndexedDB transaction; bind it to the same CAS context. */
|
|
69
|
+
export declare function sealBrowserRecord(name: string, record: BrowserPurchaseIndex | StoredBrowserPurchase | BrowserRecoveryArchiveIndex, secret: Uint8Array): Promise<EncryptedRecord>;
|
|
70
|
+
export declare const encryptedRecoveryArchiveIndexStore: (raw: Store<EncryptedRecord>, secret: Uint8Array) => Store<BrowserRecoveryArchiveIndex>;
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
/** Portable purchase backups and encrypted CAS storage. The 32-byte secret is user-held randomness. */
|
|
2
|
+
import { validateHostedRecord } from '../api/sharing.js';
|
|
3
|
+
import { purchaseStates, } from '../browser/record.js';
|
|
4
|
+
import { concat, copy } from '../envelope/pay2.js';
|
|
5
|
+
import { AnnonError } from '../errors.js';
|
|
6
|
+
import { keyFromSecret } from '../keys/near-ckd.js';
|
|
7
|
+
import { bytes, exactKeys, hex, uint, usdc, utf8 } from '../settle-near/codec.js';
|
|
8
|
+
import { identityJson } from '../settle-near/identity.js';
|
|
9
|
+
import { baseNonceV2, buildPurchaseMessage, purchaseId } from '../settle-near/purchase.js';
|
|
10
|
+
import { checkNext } from './store.js';
|
|
11
|
+
const subtle = globalThis.crypto.subtle;
|
|
12
|
+
const MAX_BYTES = 2 * 1024 * 1024;
|
|
13
|
+
const TAG = 'annon/purchase-backup/v1';
|
|
14
|
+
function validateRecord(r) {
|
|
15
|
+
if ('kind' in r && r.kind === 'browser-recovery-archives') {
|
|
16
|
+
bytes(r.purchaseId, 32);
|
|
17
|
+
if (r.id !== `recovery-archives:${r.purchaseId}` ||
|
|
18
|
+
!Number.isSafeInteger(r.revision) ||
|
|
19
|
+
r.revision < 0 ||
|
|
20
|
+
!Array.isArray(r.names) ||
|
|
21
|
+
new Set(r.names).size !== r.names.length ||
|
|
22
|
+
r.names.some((name) => typeof name !== 'string' ||
|
|
23
|
+
!name.startsWith(`recovery:${r.purchaseId}:revision:`) ||
|
|
24
|
+
!/^[0-9]+$/.test(name.slice(name.lastIndexOf(':') + 1))))
|
|
25
|
+
throw new TypeError('invalid recovery archive index');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if ('kind' in r && r.kind === 'browser-purchase') {
|
|
29
|
+
validateBrowserPurchase(r);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if ('kind' in r && r.kind === 'browser-purchase-index') {
|
|
33
|
+
if (r.id !== 'browser-index' ||
|
|
34
|
+
!Number.isSafeInteger(r.revision) ||
|
|
35
|
+
r.revision < 0 ||
|
|
36
|
+
!Array.isArray(r.entries) ||
|
|
37
|
+
new Set(r.entries.map((e) => e.id)).size !== r.entries.length)
|
|
38
|
+
throw new TypeError('invalid browser purchase index');
|
|
39
|
+
for (const entry of r.entries) {
|
|
40
|
+
if (!/^[a-zA-Z0-9_-]{1,128}$/.test(entry.id) ||
|
|
41
|
+
!Number.isSafeInteger(entry.revision) ||
|
|
42
|
+
entry.revision < 0 ||
|
|
43
|
+
!purchaseStates.includes(entry.state) ||
|
|
44
|
+
!['near', 'base', '1click'].includes(entry.route) ||
|
|
45
|
+
!Number.isSafeInteger(entry.createdAt) ||
|
|
46
|
+
!Number.isSafeInteger(entry.updatedAt) ||
|
|
47
|
+
entry.createdAt < 0 ||
|
|
48
|
+
entry.updatedAt < entry.createdAt)
|
|
49
|
+
throw new TypeError('invalid browser index entry');
|
|
50
|
+
bytes(entry.fileId, 32);
|
|
51
|
+
if (entry.purchaseId !== undefined)
|
|
52
|
+
bytes(entry.purchaseId, 32);
|
|
53
|
+
const url = new URL(entry.resourceUrl);
|
|
54
|
+
if (!['https:', 'http:'].includes(url.protocol) || url.username || url.password)
|
|
55
|
+
throw new TypeError('invalid resource URL');
|
|
56
|
+
if (Object.keys(entry).some((key) => ![
|
|
57
|
+
'id',
|
|
58
|
+
'revision',
|
|
59
|
+
'fileId',
|
|
60
|
+
'route',
|
|
61
|
+
'state',
|
|
62
|
+
'resourceUrl',
|
|
63
|
+
'purchaseId',
|
|
64
|
+
'createdAt',
|
|
65
|
+
'updatedAt',
|
|
66
|
+
].includes(key)))
|
|
67
|
+
throw new TypeError('index must contain metadata only');
|
|
68
|
+
}
|
|
69
|
+
if (utf8.encode(JSON.stringify(r)).length > MAX_BYTES)
|
|
70
|
+
throw new TypeError('browser index too large');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if ('kind' in r && r.kind === 'browser-receipt') {
|
|
74
|
+
exactKeys(r, ['kind', 'entry']);
|
|
75
|
+
validateBrowserPurchase(r.entry);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if ('kind' in r && r.kind === 'seller-key') {
|
|
79
|
+
exactKeys(r, ['kind', 'id', 'revision', 'file_id', 'seller', 'buyerSecret', 'deadline']);
|
|
80
|
+
if (r.id !== `seller-key:${r.file_id}` ||
|
|
81
|
+
!Number.isSafeInteger(r.revision) ||
|
|
82
|
+
r.revision < 0 ||
|
|
83
|
+
!Number.isSafeInteger(r.deadline) ||
|
|
84
|
+
r.deadline < 0)
|
|
85
|
+
throw new TypeError('invalid seller key record');
|
|
86
|
+
bytes(r.file_id, 32);
|
|
87
|
+
if (r.buyerSecret !== null)
|
|
88
|
+
keyFromSecret(bytes(r.buyerSecret, 32));
|
|
89
|
+
identityJson(r.seller);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if ('kind' in r && r.kind === 'sell') {
|
|
93
|
+
validateHostedRecord(r);
|
|
94
|
+
if (!Object.hasOwn(r, 'baseSplit') ||
|
|
95
|
+
!(r.baseSplit === null ||
|
|
96
|
+
(r.baseSplit &&
|
|
97
|
+
/^0x[0-9a-f]{40}$/.test(r.baseSplit.address) &&
|
|
98
|
+
['push', 'pull'].includes(r.baseSplit.kind) &&
|
|
99
|
+
/^0x[0-9a-f]{64}$/.test(r.baseSplit.salt))))
|
|
100
|
+
throw new TypeError('invalid Base split provenance');
|
|
101
|
+
if (r.id !== `sell:${r.terms.file_id}` ||
|
|
102
|
+
!Number.isSafeInteger(r.revision) ||
|
|
103
|
+
r.revision < 0 ||
|
|
104
|
+
!Number.isSafeInteger(r.deadline) ||
|
|
105
|
+
r.deadline < 0)
|
|
106
|
+
throw new TypeError('invalid sell record');
|
|
107
|
+
if (r.storageBudget !== undefined &&
|
|
108
|
+
(!/^[1-9][0-9]*$/.test(r.storageBudget) || BigInt(r.storageBudget) >= 2n ** 128n))
|
|
109
|
+
throw new TypeError('invalid sell storage budget');
|
|
110
|
+
if (r.storageFunding !== undefined &&
|
|
111
|
+
(!r.storageBudget || !['pending', 'funded', 'refused'].includes(r.storageFunding)))
|
|
112
|
+
throw new TypeError('invalid sell funding state');
|
|
113
|
+
if (r.published === true) {
|
|
114
|
+
if (r.fileKey !== null || r.sellerSecret !== null || r.ckdSecret !== null)
|
|
115
|
+
throw new TypeError('published sell retains secrets');
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
bytes(r.fileKey, 32);
|
|
119
|
+
bytes(r.sellerSecret, 32);
|
|
120
|
+
}
|
|
121
|
+
if (r.ckdSecret !== null)
|
|
122
|
+
bytes(r.ckdSecret, 48);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if ('kind' in r && r.kind === 'recovery') {
|
|
126
|
+
bytes(r.purchase_id, 32);
|
|
127
|
+
bytes(r.file_id, 32);
|
|
128
|
+
bytes(r.nonce, 32);
|
|
129
|
+
if (r.id !== `recovery:${r.purchase_id}` ||
|
|
130
|
+
hex(keyFromSecret(bytes(r.buyerSecret, 32)).publicKeyBytes) !== r.fresh_key ||
|
|
131
|
+
!Number.isSafeInteger(r.deadline) ||
|
|
132
|
+
r.deadline < 0 ||
|
|
133
|
+
!Number.isSafeInteger(r.revision) ||
|
|
134
|
+
r.revision < 0 ||
|
|
135
|
+
!(r.grant === null || (Number.isInteger(r.grant) && r.grant >= 0 && r.grant <= 0xffffffff)))
|
|
136
|
+
throw new TypeError('invalid recovery record');
|
|
137
|
+
identityJson(r.recovery);
|
|
138
|
+
}
|
|
139
|
+
else
|
|
140
|
+
validatePurchaseRecord(r);
|
|
141
|
+
}
|
|
142
|
+
function validateBrowserPurchase(entry) {
|
|
143
|
+
if (!/^[a-zA-Z0-9_-]{1,128}$/.test(entry.id) ||
|
|
144
|
+
!Number.isSafeInteger(entry.revision) ||
|
|
145
|
+
entry.revision < 0 ||
|
|
146
|
+
!purchaseStates.includes(entry.state) ||
|
|
147
|
+
!['near', 'base', '1click'].includes(entry.route) ||
|
|
148
|
+
typeof entry.header !== 'string' ||
|
|
149
|
+
!/^0x(?:[0-9a-f]{2}){1,65536}$/.test(entry.header) ||
|
|
150
|
+
(entry.needsProof !== undefined && typeof entry.needsProof !== 'boolean') ||
|
|
151
|
+
!['not_submitted', 'uncertain', 'submitted'].includes(entry.submission))
|
|
152
|
+
throw new TypeError('invalid browser purchase');
|
|
153
|
+
if (entry.paymentStarted !== undefined && typeof entry.paymentStarted !== 'boolean')
|
|
154
|
+
throw new TypeError('invalid payment boundary');
|
|
155
|
+
if (entry.lease &&
|
|
156
|
+
(!/^[a-zA-Z0-9_-]{1,128}$/.test(entry.lease.owner) ||
|
|
157
|
+
!Number.isSafeInteger(entry.lease.expiresAt) ||
|
|
158
|
+
entry.lease.expiresAt < 0))
|
|
159
|
+
throw new TypeError('invalid purchase lease');
|
|
160
|
+
if (entry.recoveryArchives !== undefined) {
|
|
161
|
+
if (!Array.isArray(entry.recoveryArchives) ||
|
|
162
|
+
new Set(entry.recoveryArchives.map((r) => r.revision)).size !== entry.recoveryArchives.length)
|
|
163
|
+
throw new TypeError('invalid recovery archives');
|
|
164
|
+
for (const archive of entry.recoveryArchives) {
|
|
165
|
+
if (archive.kind !== 'recovery')
|
|
166
|
+
throw new TypeError('invalid recovery archive');
|
|
167
|
+
validate(archive);
|
|
168
|
+
if (!entry.purchase ||
|
|
169
|
+
archive.purchase_id !== entry.purchase.id ||
|
|
170
|
+
archive.file_id !== entry.fileId)
|
|
171
|
+
throw new TypeError('recovery archive mismatch');
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
bytes(entry.fileId, 32);
|
|
175
|
+
if (uint(entry.maxPrice, 128) === 0n)
|
|
176
|
+
throw new TypeError('invalid browser price');
|
|
177
|
+
const url = new URL(entry.resourceUrl);
|
|
178
|
+
if (!['https:', 'http:'].includes(url.protocol) || url.username || url.password)
|
|
179
|
+
throw new TypeError('invalid resource URL');
|
|
180
|
+
if (entry.purchase) {
|
|
181
|
+
validatePurchaseRecord(entry.purchase);
|
|
182
|
+
if (entry.purchase.purchase.file_id !== entry.fileId ||
|
|
183
|
+
entry.purchase.purchase.route !== (entry.route === 'base' ? 'base-usdc' : 'near-usdc') ||
|
|
184
|
+
entry.purchase.payment.route !== entry.route)
|
|
185
|
+
throw new TypeError('browser purchase mismatch');
|
|
186
|
+
}
|
|
187
|
+
if (entry.recovery) {
|
|
188
|
+
validate(entry.recovery);
|
|
189
|
+
if (!entry.purchase ||
|
|
190
|
+
entry.recovery.purchase_id !== entry.purchase.id ||
|
|
191
|
+
entry.recovery.file_id !== entry.fileId)
|
|
192
|
+
throw new TypeError('browser recovery mismatch');
|
|
193
|
+
}
|
|
194
|
+
if (entry.checkpoint &&
|
|
195
|
+
(!entry.purchase ||
|
|
196
|
+
!Number.isSafeInteger(entry.checkpoint.attempts) ||
|
|
197
|
+
entry.checkpoint.attempts < 0 ||
|
|
198
|
+
!Number.isSafeInteger(entry.checkpoint.errors) ||
|
|
199
|
+
entry.checkpoint.errors < 0))
|
|
200
|
+
throw new TypeError('invalid browser checkpoint');
|
|
201
|
+
}
|
|
202
|
+
function validate(r) {
|
|
203
|
+
try {
|
|
204
|
+
validateRecord(r);
|
|
205
|
+
}
|
|
206
|
+
catch (cause) {
|
|
207
|
+
throw new AnnonError('invalid_record', 'Invalid encrypted record.', { cause });
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
export const newBackupSecret = () => crypto.getRandomValues(new Uint8Array(32));
|
|
211
|
+
function secretKey(secret) {
|
|
212
|
+
if (!(secret instanceof Uint8Array) || secret.length !== 32)
|
|
213
|
+
throw new TypeError('backup secret must be 32 random bytes held by the user');
|
|
214
|
+
return subtle.importKey('raw', copy(secret), 'HKDF', false, ['deriveKey']);
|
|
215
|
+
}
|
|
216
|
+
async function derive(secret, salt) {
|
|
217
|
+
return subtle.deriveKey({ name: 'HKDF', hash: 'SHA-256', salt: copy(salt), info: utf8.encode(TAG) }, await secretKey(secret), { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']);
|
|
218
|
+
}
|
|
219
|
+
export function validatePurchaseRecord(r) {
|
|
220
|
+
try {
|
|
221
|
+
checkPurchaseRecord(r);
|
|
222
|
+
}
|
|
223
|
+
catch (cause) {
|
|
224
|
+
throw new AnnonError('invalid_record', 'Invalid purchase record.', { cause });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function checkPurchaseRecord(r) {
|
|
228
|
+
if (typeof r.recoveryProved !== 'boolean')
|
|
229
|
+
throw new TypeError('invalid recovery proof checkpoint');
|
|
230
|
+
if (r?.schemaVersion !== 2 ||
|
|
231
|
+
!Object.hasOwn(r, 'quote') ||
|
|
232
|
+
!(r.quote === null ||
|
|
233
|
+
(r.quote &&
|
|
234
|
+
typeof r.quote === 'object' &&
|
|
235
|
+
r.quote.request &&
|
|
236
|
+
r.quote.response &&
|
|
237
|
+
r.quote.deposit)) ||
|
|
238
|
+
!r.payment ||
|
|
239
|
+
!['near', 'base', '1click'].includes(r.payment.route) ||
|
|
240
|
+
(r.payment.route === 'base') !== (r.purchase.route === 'base-usdc') ||
|
|
241
|
+
r.payment.amountBaseUnits !== r.purchase.price ||
|
|
242
|
+
r.payment.amountUsdc !== usdc(BigInt(r.purchase.price)) ||
|
|
243
|
+
r.payment.network !== r.purchase.network ||
|
|
244
|
+
r.payment.asset?.symbol !== 'USDC' ||
|
|
245
|
+
r.payment.asset.id !== r.purchase.asset ||
|
|
246
|
+
!Number.isSafeInteger(r.revision) ||
|
|
247
|
+
r.revision < 0 ||
|
|
248
|
+
r.id !==
|
|
249
|
+
purchaseId(r.purchase, r.purchase.route === 'base-usdc'
|
|
250
|
+
? { baseUsdc: r.purchase.asset }
|
|
251
|
+
: { nearUsdc: r.purchase.asset }) ||
|
|
252
|
+
r.message !==
|
|
253
|
+
buildPurchaseMessage(r.purchase, r.purchase.route === 'base-usdc'
|
|
254
|
+
? { baseUsdc: r.purchase.asset }
|
|
255
|
+
: { nearUsdc: r.purchase.asset }) ||
|
|
256
|
+
hex(keyFromSecret(bytes(r.buyerSecret, 32)).publicKeyBytes) !== r.purchase.buyer_key ||
|
|
257
|
+
identityJson(r.recovery) !== identityJson(r.purchase.recovery) ||
|
|
258
|
+
(r.paymentState !== undefined && !['expired', 'canceled'].includes(r.paymentState)) ||
|
|
259
|
+
!Array.isArray(r.attempts) ||
|
|
260
|
+
!r.terms ||
|
|
261
|
+
r.terms.network !== r.purchase.network ||
|
|
262
|
+
r.terms.usdc !== r.purchase.asset ||
|
|
263
|
+
r.terms.price !== r.purchase.price ||
|
|
264
|
+
r.terms.nonce !== (r.purchase.route === 'base-usdc' ? baseNonceV2(r.id) : r.purchase.nonce) ||
|
|
265
|
+
r.terms.contractId !== r.purchase.contract ||
|
|
266
|
+
r.terms.fileId !== r.purchase.file_id ||
|
|
267
|
+
r.terms.buyerKey !== r.purchase.buyer_key ||
|
|
268
|
+
r.terms.headerHash !== r.checkedHeaderHash)
|
|
269
|
+
throw new TypeError('invalid purchase record');
|
|
270
|
+
bytes(r.checkedHeaderHash, 32);
|
|
271
|
+
if (utf8.encode(JSON.stringify(r)).length > MAX_BYTES)
|
|
272
|
+
throw new TypeError('purchase record too large');
|
|
273
|
+
}
|
|
274
|
+
async function encrypt(r, secret, context = '') {
|
|
275
|
+
validate(r);
|
|
276
|
+
const salt = crypto.getRandomValues(new Uint8Array(32));
|
|
277
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
278
|
+
const ciphertext = await subtle.encrypt({
|
|
279
|
+
name: 'AES-GCM',
|
|
280
|
+
iv,
|
|
281
|
+
additionalData: concat(utf8.encode(TAG), utf8.encode(context)),
|
|
282
|
+
tagLength: 128,
|
|
283
|
+
}, await derive(secret, salt), utf8.encode(JSON.stringify(r)));
|
|
284
|
+
const out = {
|
|
285
|
+
format: 'annon-purchase-backup',
|
|
286
|
+
v: 1,
|
|
287
|
+
kdf: 'HKDF-SHA256',
|
|
288
|
+
salt: hex(salt),
|
|
289
|
+
iv: hex(iv),
|
|
290
|
+
ciphertext: hex(new Uint8Array(ciphertext)),
|
|
291
|
+
};
|
|
292
|
+
return JSON.stringify(out);
|
|
293
|
+
}
|
|
294
|
+
async function decrypt(file, secret, context = '') {
|
|
295
|
+
if (typeof file !== 'string' || file.length > MAX_BYTES * 2 + 1024)
|
|
296
|
+
throw new TypeError('backup too large');
|
|
297
|
+
const b = JSON.parse(file);
|
|
298
|
+
exactKeys(b, ['format', 'v', 'kdf', 'salt', 'iv', 'ciphertext']);
|
|
299
|
+
if (b.format !== 'annon-purchase-backup' ||
|
|
300
|
+
b.v !== 1 ||
|
|
301
|
+
b.kdf !== 'HKDF-SHA256' ||
|
|
302
|
+
typeof b.ciphertext !== 'string' ||
|
|
303
|
+
!/^0x(?:[0-9a-f]{2}){16,}$/.test(b.ciphertext))
|
|
304
|
+
throw new TypeError('unsupported backup format');
|
|
305
|
+
const raw = bytes(b.ciphertext, (b.ciphertext.length - 2) / 2);
|
|
306
|
+
const plain = await subtle.decrypt({
|
|
307
|
+
name: 'AES-GCM',
|
|
308
|
+
iv: bytes(b.iv, 12),
|
|
309
|
+
additionalData: concat(utf8.encode(TAG), utf8.encode(context)),
|
|
310
|
+
tagLength: 128,
|
|
311
|
+
}, await derive(secret, bytes(b.salt, 32)), raw);
|
|
312
|
+
const r = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(plain));
|
|
313
|
+
validate(r);
|
|
314
|
+
return r;
|
|
315
|
+
}
|
|
316
|
+
export const exportPurchase = (record, secret) => encrypt(record, secret);
|
|
317
|
+
export const importPurchase = async (file, secret) => {
|
|
318
|
+
const r = await decrypt(file, secret);
|
|
319
|
+
if ('kind' in r)
|
|
320
|
+
throw new TypeError('not a purchase');
|
|
321
|
+
return r;
|
|
322
|
+
};
|
|
323
|
+
export const exportRecovery = (record, secret) => encrypt(record, secret);
|
|
324
|
+
export const importRecovery = async (file, secret) => {
|
|
325
|
+
const r = await decrypt(file, secret);
|
|
326
|
+
if (!('kind' in r) || r.kind !== 'recovery')
|
|
327
|
+
throw new TypeError('not a recovery');
|
|
328
|
+
return r;
|
|
329
|
+
};
|
|
330
|
+
/** Wrap memory, IndexedDB, fileStore or an existing durable store; keep only ciphertext at rest. */
|
|
331
|
+
function encryptedStore(raw, secret) {
|
|
332
|
+
if (secret.length !== 32)
|
|
333
|
+
throw new TypeError('backup secret must be 32 bytes');
|
|
334
|
+
const key = copy(secret);
|
|
335
|
+
const context = (name, v) => JSON.stringify([name, v.id, v.revision]);
|
|
336
|
+
return {
|
|
337
|
+
async load(name) {
|
|
338
|
+
const encrypted = await raw.load(name);
|
|
339
|
+
if (!encrypted)
|
|
340
|
+
return null;
|
|
341
|
+
const r = await decrypt(encrypted.backup, key, context(name, encrypted));
|
|
342
|
+
if (!('id' in r) || r.id !== encrypted.id || r.revision !== encrypted.revision)
|
|
343
|
+
throw new Error('encrypted record metadata mismatch');
|
|
344
|
+
return r;
|
|
345
|
+
},
|
|
346
|
+
async compareAndSet(name, expected, next) {
|
|
347
|
+
checkNext(expected, next);
|
|
348
|
+
const value = next
|
|
349
|
+
? {
|
|
350
|
+
id: next.id,
|
|
351
|
+
revision: next.revision,
|
|
352
|
+
backup: await encrypt(next, key, context(name, next)),
|
|
353
|
+
}
|
|
354
|
+
: null;
|
|
355
|
+
return raw.compareAndSet(name, expected, value);
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
export const encryptedPurchaseStore = (raw, secret) => encryptedStore(raw, secret);
|
|
360
|
+
export const encryptedRecoveryStore = (raw, secret) => encryptedStore(raw, secret);
|
|
361
|
+
export const exportSell = (record, secret) => encrypt(record, secret);
|
|
362
|
+
export const importSell = async (file, secret) => {
|
|
363
|
+
const r = await decrypt(file, secret);
|
|
364
|
+
if (!('kind' in r) || r.kind !== 'sell')
|
|
365
|
+
throw new TypeError('not a sell record');
|
|
366
|
+
return r;
|
|
367
|
+
};
|
|
368
|
+
export const encryptedSellStore = (raw, secret) => encryptedStore(raw, secret);
|
|
369
|
+
export const exportSellerKey = (record, secret) => encrypt(record, secret);
|
|
370
|
+
export async function importSellerKey(file, secret) {
|
|
371
|
+
try {
|
|
372
|
+
const record = await decrypt(file, secret);
|
|
373
|
+
if (!('kind' in record) || record.kind !== 'seller-key')
|
|
374
|
+
throw new AnnonError('invalid_record');
|
|
375
|
+
return record;
|
|
376
|
+
}
|
|
377
|
+
catch (cause) {
|
|
378
|
+
if (cause instanceof AnnonError && cause.code === 'invalid_record')
|
|
379
|
+
throw cause;
|
|
380
|
+
throw new AnnonError('invalid_record', undefined, { cause });
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
export const encryptedSellerKeyStore = (raw, secret) => encryptedStore(raw, secret);
|
|
384
|
+
/** Browser workflow records use the same authenticated encryption and CAS binding as purchases. */
|
|
385
|
+
export const exportBrowserReceipt = (record, secret) => encrypt(record, secret);
|
|
386
|
+
export async function importBrowserReceipt(file, secret) {
|
|
387
|
+
const record = await decrypt(file, secret);
|
|
388
|
+
if (!('kind' in record) || record.kind !== 'browser-receipt')
|
|
389
|
+
throw new AnnonError('invalid_record');
|
|
390
|
+
return record;
|
|
391
|
+
}
|
|
392
|
+
export const encryptedBrowserIndexStore = (raw, secret) => encryptedStore(raw, secret);
|
|
393
|
+
export const encryptedBrowserPurchaseStore = (raw, secret) => encryptedStore(raw, secret);
|
|
394
|
+
/** Prepare ciphertext outside an IndexedDB transaction; bind it to the same CAS context. */
|
|
395
|
+
export async function sealBrowserRecord(name, record, secret) {
|
|
396
|
+
return {
|
|
397
|
+
id: record.id,
|
|
398
|
+
revision: record.revision,
|
|
399
|
+
backup: await encrypt(record, secret, JSON.stringify([name, record.id, record.revision])),
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
export const encryptedRecoveryArchiveIndexStore = (raw, secret) => encryptedStore(raw, secret);
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { AnnonError } from '../errors.js';
|
|
2
|
+
import { type Authorization } from './eip3009.js';
|
|
3
|
+
import type { PurchaseRecord } from './purchase.js';
|
|
4
|
+
export type PayErrorCode =
|
|
5
|
+
/** The RPC couldn't be reached or answered something malformed; safe to retry. */
|
|
6
|
+
'transport' | 'base_rpc_rate_limited' | 'refused'
|
|
7
|
+
/** The payment request or the registration don't allow this purchase; nothing was signed. */
|
|
8
|
+
| 'storage_required' | 'unknown_file' | 'taken_down' | 'sales_stopped' | 'unknown_moderator' | 'price_mismatch' | 'price_above_max' | 'file_storage_low' | 'protocol_fee_above_max' | 'recipient_mismatch' | 'file_mismatch' | 'network_mismatch' | 'token_mismatch'
|
|
9
|
+
/** The offer's transfer method isn't the one the registration needs. */
|
|
10
|
+
| 'method_mismatch'
|
|
11
|
+
/** The payment request names an Annon contract this client doesn't trust. */
|
|
12
|
+
| 'untrusted_contract'
|
|
13
|
+
/** The PAY2 header (or the public file) isn't the registered file signed by its seller. */
|
|
14
|
+
| 'file_invalid' | 'route_unavailable' | 'bad_requirements' | 'insufficient_balance' | 'smart_wallet_unsupported'
|
|
15
|
+
/** The wallet's answer isn't a signature USDC accepts from this buyer. */
|
|
16
|
+
| 'bad_signature'
|
|
17
|
+
/** USDC refused the signed authorization in a dry run (the RPC saw it; see `PayError.purchase`). */
|
|
18
|
+
| 'payment_rejected'
|
|
19
|
+
/** This wallet has an open purchase of this file: follow `nextStep` of the recorded one. */
|
|
20
|
+
| 'purchase_open'
|
|
21
|
+
/** The purchase record changed under this writer (another tab or process): load it again. */
|
|
22
|
+
| 'store_conflict'
|
|
23
|
+
/** A seller: this payment id came with another payload before (HTTP 409). */
|
|
24
|
+
| 'payment_id_conflict'
|
|
25
|
+
/** A seller: the settlement of this purchase is under way or unclear; send the same payload later. */
|
|
26
|
+
| 'settlement_pending' | 'not_sponsored' | 'wrong_identity' | 'relayer_unavailable' | 'facilitator_busy' | 'unavailable'
|
|
27
|
+
/** A seller: this purchase can no longer be paid (expired or canceled, finalized). */
|
|
28
|
+
| 'payment_expired';
|
|
29
|
+
export declare class PayError extends AnnonError {
|
|
30
|
+
readonly code: PayErrorCode;
|
|
31
|
+
/**
|
|
32
|
+
* Set when the wallet had already signed: the authorization may have reached an RPC and could
|
|
33
|
+
* still be settled. The purchase stays open: follow `nextStep`.
|
|
34
|
+
*/
|
|
35
|
+
purchase?: PurchaseRecord;
|
|
36
|
+
retryAfterSeconds?: number;
|
|
37
|
+
constructor(code: PayErrorCode, message?: string);
|
|
38
|
+
}
|
|
39
|
+
/** A typed RPC rate limit; callers retry through isTransient. */
|
|
40
|
+
export declare class BaseRpcRateLimited extends PayError {
|
|
41
|
+
constructor();
|
|
42
|
+
}
|
|
43
|
+
export declare const fail: (code: PayErrorCode, message?: string) => never;
|
|
44
|
+
interface Head {
|
|
45
|
+
number: number;
|
|
46
|
+
timestamp: number;
|
|
47
|
+
/** 0x and lowercase, when the RPC gave it. */
|
|
48
|
+
hash?: string;
|
|
49
|
+
}
|
|
50
|
+
/** The latest block, after checking the RPC is on Base mainnet. */
|
|
51
|
+
export declare function head(url: string): Promise<Head>;
|
|
52
|
+
/** A block by number or tag (`latest`, `safe`, `finalized`). */
|
|
53
|
+
export declare function block(url: string, at: number | 'latest' | 'safe' | 'finalized'): Promise<Head>;
|
|
54
|
+
/** A transaction's receipt as the RPC returns it; null while the transaction is unknown. */
|
|
55
|
+
export declare const getReceipt: (url: string, txHash: string) => Promise<unknown>;
|
|
56
|
+
/** A view call at `block` that returns one 32-byte word. */
|
|
57
|
+
export declare function viewWord(url: string, to: string, data: string, block: number): Promise<string>;
|
|
58
|
+
export declare const balanceOf: (url: string, usdc: string, owner: string, block: number) => Promise<bigint>;
|
|
59
|
+
/** USDC's `authorizationState`: true once the nonce was used *or* canceled. A hint, not proof. */
|
|
60
|
+
export declare const authorizationState: (url: string, usdc: string, authorizer: string, nonce: string, block: number) => Promise<boolean>;
|
|
61
|
+
/** Whether an address has code: a contract, or an EOA delegated with EIP-7702. */
|
|
62
|
+
/** The runtime code at `address`, lowercase hex; `0x` for none. */
|
|
63
|
+
export declare function getCode(url: string, address: string, block: number): Promise<string>;
|
|
64
|
+
export declare const hasCode: (url: string, address: string, block: number) => Promise<boolean>;
|
|
65
|
+
/** `transferWithAuthorization(from, to, value, after, before, nonce, v, r, s)`. */
|
|
66
|
+
export declare function authorizationCall(a: Authorization, signature: string): string;
|
|
67
|
+
/**
|
|
68
|
+
* Runs the signed authorization against USDC without sending it: signature, nonce, validity
|
|
69
|
+
* window, balance and blacklist, all by USDC's own code.
|
|
70
|
+
*/
|
|
71
|
+
export declare function dryRun(url: string, usdc: string, a: Authorization, signature: string): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* The RLP header of the block `hash`, rebuilt from the RPC's fields and checked to hash to it.
|
|
74
|
+
* `unlock` takes it for a file whose sales were stopped: the contract reads the payment's time
|
|
75
|
+
* from it. A header with a field this SDK doesn't know fails here, never on NEAR.
|
|
76
|
+
*/
|
|
77
|
+
export declare function blockHeader(url: string, hash: string): Promise<string>;
|
|
78
|
+
/** A log as the contract's `unlock` takes it (NEAR MPC's `EvmLog`). */
|
|
79
|
+
export interface EvmLog {
|
|
80
|
+
address: string;
|
|
81
|
+
block_hash: string;
|
|
82
|
+
block_number: number;
|
|
83
|
+
data: string;
|
|
84
|
+
log_index: number;
|
|
85
|
+
removed: boolean;
|
|
86
|
+
topics: string[];
|
|
87
|
+
transaction_hash: string;
|
|
88
|
+
transaction_index: number;
|
|
89
|
+
}
|
|
90
|
+
/** What a purchase expects to find on Base: the job saved before the buyer signed. */
|
|
91
|
+
export interface Expected {
|
|
92
|
+
usdc: string;
|
|
93
|
+
/** The registered `pay_to`. */
|
|
94
|
+
to: string;
|
|
95
|
+
from: string;
|
|
96
|
+
/** The signed nonce. */
|
|
97
|
+
nonce: string;
|
|
98
|
+
price: bigint;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The logs of this purchase in a transaction receipt, exactly as `unlock` checks them: USDC's
|
|
102
|
+
* `AuthorizationUsed` for the nonce and the `Transfer` to `pay_to` right after it. Null when the
|
|
103
|
+
* receipt holds no such payment.
|
|
104
|
+
*/
|
|
105
|
+
export declare function paymentLogs(receipt: unknown, e: Expected): EvmLog[] | null;
|
|
106
|
+
export type PaymentStatus =
|
|
107
|
+
/** On Base: `logs` go to `unlock`; `logIndex` identifies its first proved log. */
|
|
108
|
+
{
|
|
109
|
+
state: 'paid';
|
|
110
|
+
txHash: string;
|
|
111
|
+
logIndex: number;
|
|
112
|
+
logs: EvmLog[];
|
|
113
|
+
}
|
|
114
|
+
/** Not on Base yet and still valid: wait, or send the same header again. */
|
|
115
|
+
| {
|
|
116
|
+
state: 'pending';
|
|
117
|
+
}
|
|
118
|
+
/** Can never be paid: a new purchase with a new one-time key is safe. */
|
|
119
|
+
| {
|
|
120
|
+
state: 'expired';
|
|
121
|
+
} | {
|
|
122
|
+
state: 'canceled';
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Where a signed purchase stands, found by its nonce alone, so a lost transaction hash loses
|
|
126
|
+
* nothing. `block` is the head when it was signed. A payment counts as soon as it is in a block;
|
|
127
|
+
* `expired` and `canceled` only from the finalized chain, because a new purchase follows them and
|
|
128
|
+
* a reorg must not turn that into a second payment. `authorizationState` is only read to catch an
|
|
129
|
+
* RPC that misses logs.
|
|
130
|
+
*/
|
|
131
|
+
export declare function paymentStatus(url: string, e: Expected & {
|
|
132
|
+
block: number;
|
|
133
|
+
validBefore: bigint;
|
|
134
|
+
}): Promise<PaymentStatus>;
|
|
135
|
+
export {};
|