@defdone/annon 0.0.0-stage → 0.1.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/CLI.md +378 -0
- package/LICENSE +201 -0
- package/NOTICE +7 -0
- package/README.md +581 -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 +116 -0
- package/dist/browser/login.d.ts +1 -0
- package/dist/browser/login.js +177 -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 +99 -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 +18 -0
- package/dist/node/browser-login.js +212 -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 +147 -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 +393 -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 +135 -3
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Atomic, synced records with per-key CAS locks and exclusive service-directory ownership. */
|
|
2
|
+
import { type Store, type Versioned } from './store.js';
|
|
3
|
+
export declare function fileStore<T extends Versioned>(dir: string, o?: {
|
|
4
|
+
lockWaitMs?: number;
|
|
5
|
+
recoverDeadLocks?: boolean;
|
|
6
|
+
}): Store<T>;
|
|
7
|
+
/** Call before opening service stores; release only after all in-flight writes have drained. */
|
|
8
|
+
export declare function ownDirectory(dir: string): Promise<() => Promise<void>>;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/** Atomic, synced records with per-key CAS locks and exclusive service-directory ownership. */
|
|
2
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
3
|
+
import { link, mkdir, open, readdir, readFile, rename, rmdir, unlink } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { sleep } from '../net/retry.js';
|
|
6
|
+
import { syncDir } from '../node/sync-dir.js';
|
|
7
|
+
import { checkNext, holds } from './store.js';
|
|
8
|
+
const alive = (pid) => {
|
|
9
|
+
try {
|
|
10
|
+
process.kill(pid, 0);
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
catch (e) {
|
|
14
|
+
// EPERM: it runs, as another user.
|
|
15
|
+
return e.code !== 'ESRCH';
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
const started = async (pid) => {
|
|
19
|
+
try {
|
|
20
|
+
const stat = await readFile(`/proc/${pid}/stat`, 'utf8');
|
|
21
|
+
const boot = await readFile('/proc/sys/kernel/random/boot_id', 'utf8');
|
|
22
|
+
return `${boot.trim()}|${stat
|
|
23
|
+
.slice(stat.lastIndexOf(')') + 1)
|
|
24
|
+
.trim()
|
|
25
|
+
.split(/\s+/)[19]}`;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return '';
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const ownerOf = (text) => {
|
|
32
|
+
try {
|
|
33
|
+
const value = JSON.parse(text);
|
|
34
|
+
return typeof value === 'number' ? { pid: value } : value;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return { pid: 0 };
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const ownerAlive = async (o) => alive(o.pid) && (!o.started || o.started === (await started(o.pid)));
|
|
41
|
+
export function fileStore(dir, o = {}) {
|
|
42
|
+
const wait = o.lockWaitMs ?? 5_000;
|
|
43
|
+
const fileOf = (key) => join(dir, `${createHash('sha256').update(key).digest('hex')}.json`);
|
|
44
|
+
const load = async (file) => {
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(await readFile(file, 'utf8'));
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
if (e.code === 'ENOENT')
|
|
50
|
+
return null;
|
|
51
|
+
throw e;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
/** Writes `data` to a new temporary file, synced, and returns its name. */
|
|
55
|
+
const temp = async (file, data) => {
|
|
56
|
+
const t = `${file}.${randomBytes(6).toString('hex')}.tmp`;
|
|
57
|
+
// Owner only: a record holds a signed authorization anyone could submit.
|
|
58
|
+
const h = await open(t, 'wx', 0o600);
|
|
59
|
+
try {
|
|
60
|
+
await h.writeFile(data);
|
|
61
|
+
await h.sync();
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
await h.close();
|
|
65
|
+
}
|
|
66
|
+
return t;
|
|
67
|
+
};
|
|
68
|
+
const lock = async (file) => {
|
|
69
|
+
const l = `${file}.lock`;
|
|
70
|
+
// The lock appears with its owner's pid already in it: `link` of a complete file is atomic and
|
|
71
|
+
// fails if the lock exists.
|
|
72
|
+
const metadata = { pid: process.pid, started: await started(process.pid) };
|
|
73
|
+
const mine = await temp(file, JSON.stringify(metadata));
|
|
74
|
+
try {
|
|
75
|
+
for (const until = Date.now() + wait;;) {
|
|
76
|
+
try {
|
|
77
|
+
await link(mine, l);
|
|
78
|
+
return l;
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
if (e.code !== 'EEXIST')
|
|
82
|
+
throw e;
|
|
83
|
+
}
|
|
84
|
+
if (o.recoverDeadLocks) {
|
|
85
|
+
const owner = ownerOf(await readFile(l, 'utf8').catch(() => ''));
|
|
86
|
+
if (Number.isSafeInteger(owner.pid) && owner.pid > 0 && !(await ownerAlive(owner))) {
|
|
87
|
+
// Each contender has its own atomic choosing/ticket file. A dead contender can be
|
|
88
|
+
// removed without ever unlinking a successor's coordination file.
|
|
89
|
+
const prefix = `${file.slice(file.lastIndexOf('/') + 1)}.recovery.`;
|
|
90
|
+
const participant = `${l.slice(0, -5)}.recovery.${process.pid}.${randomBytes(8).toString('hex')}.participant`;
|
|
91
|
+
const peers = async () => {
|
|
92
|
+
const result = [];
|
|
93
|
+
for (const name of await readdir(dir)) {
|
|
94
|
+
if (!name.startsWith(prefix) || !name.endsWith('.participant'))
|
|
95
|
+
continue;
|
|
96
|
+
const path = join(dir, name);
|
|
97
|
+
let peer;
|
|
98
|
+
try {
|
|
99
|
+
peer = JSON.parse(await readFile(path, 'utf8'));
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
if (e.code === 'ENOENT')
|
|
103
|
+
continue;
|
|
104
|
+
throw e;
|
|
105
|
+
}
|
|
106
|
+
if (!Number.isSafeInteger(peer.pid) ||
|
|
107
|
+
peer.pid <= 0 ||
|
|
108
|
+
!Number.isSafeInteger(peer.ticket) ||
|
|
109
|
+
peer.ticket < 0 ||
|
|
110
|
+
typeof peer.choosing !== 'boolean')
|
|
111
|
+
throw new Error('invalid store recovery state');
|
|
112
|
+
if (!(await ownerAlive(peer))) {
|
|
113
|
+
await unlink(path).catch((e) => {
|
|
114
|
+
if (e.code !== 'ENOENT')
|
|
115
|
+
throw e;
|
|
116
|
+
});
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
result.push({ ...peer, name });
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
};
|
|
123
|
+
const choosing = await temp(file, JSON.stringify({ ...metadata, choosing: true, ticket: 0 }));
|
|
124
|
+
await rename(choosing, participant);
|
|
125
|
+
try {
|
|
126
|
+
const ticket = 1 + Math.max(0, ...(await peers()).map((p) => p.ticket));
|
|
127
|
+
await rename(await temp(file, JSON.stringify({ ...metadata, choosing: false, ticket })), participant);
|
|
128
|
+
const name = participant.slice(participant.lastIndexOf('/') + 1);
|
|
129
|
+
while ((await peers()).some((p) => p.name !== name &&
|
|
130
|
+
(p.choosing || p.ticket < ticket || (p.ticket === ticket && p.name < name)))) {
|
|
131
|
+
if (Date.now() > until)
|
|
132
|
+
throw new Error('the store is busy: recovery is running');
|
|
133
|
+
await sleep(20);
|
|
134
|
+
}
|
|
135
|
+
const current = ownerOf(await readFile(l, 'utf8').catch(() => ''));
|
|
136
|
+
if (Number.isSafeInteger(current.pid) &&
|
|
137
|
+
current.pid > 0 &&
|
|
138
|
+
!(await ownerAlive(current)))
|
|
139
|
+
await unlink(l).catch((e) => {
|
|
140
|
+
if (e.code !== 'ENOENT')
|
|
141
|
+
throw e;
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
await unlink(participant).catch(() => { });
|
|
146
|
+
}
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (Date.now() > until) {
|
|
151
|
+
const owner = ownerOf(await readFile(l, 'utf8').catch(() => ''));
|
|
152
|
+
throw new Error(Number.isSafeInteger(owner.pid) && owner.pid > 0 && !(await ownerAlive(owner))
|
|
153
|
+
? `the store is locked by ${l}, left by process ${owner.pid}, which is not running: remove that file once no other process uses the store`
|
|
154
|
+
: `the store is busy: ${l} is held by a running process`);
|
|
155
|
+
}
|
|
156
|
+
await sleep(20);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
await unlink(mine).catch(() => { });
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
return {
|
|
164
|
+
load: (key) => load(fileOf(key)),
|
|
165
|
+
compareAndSet: async (key, expected, next) => {
|
|
166
|
+
checkNext(expected, next);
|
|
167
|
+
const file = fileOf(key);
|
|
168
|
+
const l = await lock(file);
|
|
169
|
+
try {
|
|
170
|
+
if (!holds(await load(file), expected))
|
|
171
|
+
return false;
|
|
172
|
+
if (next === null)
|
|
173
|
+
await unlink(file);
|
|
174
|
+
else
|
|
175
|
+
await rename(await temp(file, JSON.stringify(next)), file);
|
|
176
|
+
await syncDir(dir);
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
await unlink(l);
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/** Call before opening service stores; release only after all in-flight writes have drained. */
|
|
186
|
+
export async function ownDirectory(dir) {
|
|
187
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
188
|
+
const store = fileStore(dir, {
|
|
189
|
+
lockWaitMs: 0,
|
|
190
|
+
recoverDeadLocks: true,
|
|
191
|
+
});
|
|
192
|
+
const key = 'exclusive-process-owner';
|
|
193
|
+
const old = await store.load(key);
|
|
194
|
+
if (old && (await ownerAlive(old)))
|
|
195
|
+
throw new Error('the store is busy: another process owns it');
|
|
196
|
+
const mine = {
|
|
197
|
+
id: key,
|
|
198
|
+
revision: (old?.revision ?? -1) + 1,
|
|
199
|
+
pid: process.pid,
|
|
200
|
+
started: await started(process.pid),
|
|
201
|
+
};
|
|
202
|
+
if (!(await store.compareAndSet(key, old, mine)))
|
|
203
|
+
throw new Error('the store is busy: another process owns it');
|
|
204
|
+
const sweep = async (path) => {
|
|
205
|
+
for (const entry of await readdir(path, { withFileTypes: true })) {
|
|
206
|
+
const file = join(path, entry.name);
|
|
207
|
+
if (entry.isDirectory()) {
|
|
208
|
+
if (entry.name.endsWith('.lock.recovery'))
|
|
209
|
+
await rmdir(file);
|
|
210
|
+
else
|
|
211
|
+
await sweep(file);
|
|
212
|
+
}
|
|
213
|
+
else if (entry.name.endsWith('.lock') ||
|
|
214
|
+
entry.name.endsWith('.tmp') ||
|
|
215
|
+
entry.name.endsWith('.participant'))
|
|
216
|
+
await unlink(file);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
await sweep(dir);
|
|
220
|
+
let released;
|
|
221
|
+
return () => (released ??= (async () => {
|
|
222
|
+
await store.compareAndSet(key, mine, null);
|
|
223
|
+
})());
|
|
224
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a purchase in progress is kept (x402-annon.md sections 5 and 10): by the buyer's client, and by a seller
|
|
3
|
+
* for the payments it settles. A store has two operations; `compareAndSet` stops a stale tab or process from overwriting a newer record. Ready-made stores: `memoryStore` (one process), `indexedDbStore` (a browser), `fileStore`
|
|
4
|
+
* in `store-node.ts` (Node, CLI, agents), and `fromKeyValue` for a store you already have.
|
|
5
|
+
*/
|
|
6
|
+
/** A record a store keeps: its id, and how often it was written since it was created (from 0). */
|
|
7
|
+
export interface Versioned {
|
|
8
|
+
id: string;
|
|
9
|
+
revision: number;
|
|
10
|
+
}
|
|
11
|
+
export interface Store<T extends Versioned> {
|
|
12
|
+
load(key: string): Promise<T | null>;
|
|
13
|
+
/**
|
|
14
|
+
* Writes `next` under `key`, or removes the key when `next` is null, only if the key holds the
|
|
15
|
+
* record `expected` names (its id and revision), or nothing when `expected` is null. Atomic: of
|
|
16
|
+
* two writers that read the same state, one gets true. Resolves only once the write is durable.
|
|
17
|
+
*/
|
|
18
|
+
compareAndSet(key: string, expected: Versioned | null, next: T | null): Promise<boolean>;
|
|
19
|
+
}
|
|
20
|
+
export interface StoreWrite<T extends Versioned> {
|
|
21
|
+
key: string;
|
|
22
|
+
expected: Versioned | null;
|
|
23
|
+
next: T | null;
|
|
24
|
+
}
|
|
25
|
+
export interface AtomicStore<T extends Versioned> extends Store<T> {
|
|
26
|
+
/** All comparisons and writes commit together, or none do. */
|
|
27
|
+
compareAndSetMany(writes: readonly StoreWrite<T>[]): Promise<boolean>;
|
|
28
|
+
}
|
|
29
|
+
/** `next` must be `expected`'s next revision: a create starts at 0, an update adds 1. */
|
|
30
|
+
export declare function checkNext(expected: Versioned | null, next: Versioned | null): void;
|
|
31
|
+
export declare const holds: (current: Versioned | null, expected: Versioned | null) => boolean;
|
|
32
|
+
/** One process, in memory: for tests, a short-lived agent, or a server with one instance. */
|
|
33
|
+
export declare function memoryStore<T extends Versioned>(): Store<T>;
|
|
34
|
+
export interface KeyValue {
|
|
35
|
+
get(key: string): Promise<string | null>;
|
|
36
|
+
set(key: string, value: string): Promise<void>;
|
|
37
|
+
remove(key: string): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A store over a plain key-value store. `runExclusive` must run `fn` with no other `runExclusive`
|
|
41
|
+
* for the same key running anywhere that shares the store (e.g. a database row lock, or the Web
|
|
42
|
+
* Locks API for the tabs of one origin): the whole read, check and write run inside it.
|
|
43
|
+
*/
|
|
44
|
+
export declare function fromKeyValue<T extends Versioned>(kv: KeyValue, runExclusive: <R>(key: string, fn: () => Promise<R>) => Promise<R>): Store<T>;
|
|
45
|
+
/**
|
|
46
|
+
* A browser store: one IndexedDB object store, each check and write in one `readwrite`
|
|
47
|
+
* transaction (IndexedDB runs those one at a time for a store, across the tabs of an origin),
|
|
48
|
+
* committed with `durability: 'strict'`. What a browser keeps is still the user's: clearing site
|
|
49
|
+
* data removes it.
|
|
50
|
+
*/
|
|
51
|
+
export declare function indexedDbStore<T extends Versioned>(o?: {
|
|
52
|
+
name?: string;
|
|
53
|
+
factory?: IDBFactory;
|
|
54
|
+
}): AtomicStore<T>;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a purchase in progress is kept (x402-annon.md sections 5 and 10): by the buyer's client, and by a seller
|
|
3
|
+
* for the payments it settles. A store has two operations; `compareAndSet` stops a stale tab or process from overwriting a newer record. Ready-made stores: `memoryStore` (one process), `indexedDbStore` (a browser), `fileStore`
|
|
4
|
+
* in `store-node.ts` (Node, CLI, agents), and `fromKeyValue` for a store you already have.
|
|
5
|
+
*/
|
|
6
|
+
/** `next` must be `expected`'s next revision: a create starts at 0, an update adds 1. */
|
|
7
|
+
export function checkNext(expected, next) {
|
|
8
|
+
if (next === null) {
|
|
9
|
+
if (expected === null)
|
|
10
|
+
throw new TypeError('nothing to remove');
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (next.revision !== (expected === null ? 0 : expected.revision + 1)) {
|
|
14
|
+
throw new TypeError("next must be the expected record's next revision");
|
|
15
|
+
}
|
|
16
|
+
if (expected !== null && next.id !== expected.id) {
|
|
17
|
+
throw new TypeError('next must keep the id of the record it replaces');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export const holds = (current, expected) => expected === null
|
|
21
|
+
? current === null
|
|
22
|
+
: current !== null && current.id === expected.id && current.revision === expected.revision;
|
|
23
|
+
const read = (s) => s === null || s === undefined ? null : JSON.parse(s);
|
|
24
|
+
/** One process, in memory: for tests, a short-lived agent, or a server with one instance. */
|
|
25
|
+
export function memoryStore() {
|
|
26
|
+
const m = new Map();
|
|
27
|
+
return {
|
|
28
|
+
load: async (key) => read(m.get(key)),
|
|
29
|
+
compareAndSet: async (key, expected, next) => {
|
|
30
|
+
checkNext(expected, next);
|
|
31
|
+
if (!holds(read(m.get(key)), expected))
|
|
32
|
+
return false;
|
|
33
|
+
if (next === null)
|
|
34
|
+
m.delete(key);
|
|
35
|
+
else
|
|
36
|
+
m.set(key, JSON.stringify(next));
|
|
37
|
+
return true;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A store over a plain key-value store. `runExclusive` must run `fn` with no other `runExclusive`
|
|
43
|
+
* for the same key running anywhere that shares the store (e.g. a database row lock, or the Web
|
|
44
|
+
* Locks API for the tabs of one origin): the whole read, check and write run inside it.
|
|
45
|
+
*/
|
|
46
|
+
export function fromKeyValue(kv, runExclusive) {
|
|
47
|
+
return {
|
|
48
|
+
load: async (key) => read(await kv.get(key)),
|
|
49
|
+
compareAndSet: async (key, expected, next) => {
|
|
50
|
+
checkNext(expected, next);
|
|
51
|
+
return runExclusive(key, async () => {
|
|
52
|
+
if (!holds(read(await kv.get(key)), expected))
|
|
53
|
+
return false;
|
|
54
|
+
if (next === null)
|
|
55
|
+
await kv.remove(key);
|
|
56
|
+
else
|
|
57
|
+
await kv.set(key, JSON.stringify(next));
|
|
58
|
+
return true;
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* A browser store: one IndexedDB object store, each check and write in one `readwrite`
|
|
65
|
+
* transaction (IndexedDB runs those one at a time for a store, across the tabs of an origin),
|
|
66
|
+
* committed with `durability: 'strict'`. What a browser keeps is still the user's: clearing site
|
|
67
|
+
* data removes it.
|
|
68
|
+
*/
|
|
69
|
+
export function indexedDbStore(o = {}) {
|
|
70
|
+
const factory = o.factory ?? globalThis.indexedDB;
|
|
71
|
+
if (!factory)
|
|
72
|
+
throw new TypeError('no IndexedDB here: pass a factory or use another store');
|
|
73
|
+
const name = o.name ?? 'annon';
|
|
74
|
+
let db;
|
|
75
|
+
const open = () => {
|
|
76
|
+
db ??= new Promise((resolve, reject) => {
|
|
77
|
+
const r = factory.open(name, 1);
|
|
78
|
+
r.onupgradeneeded = () => r.result.createObjectStore('records');
|
|
79
|
+
r.onsuccess = () => resolve(r.result);
|
|
80
|
+
r.onerror = () => reject(r.error);
|
|
81
|
+
});
|
|
82
|
+
return db;
|
|
83
|
+
};
|
|
84
|
+
const tx = async (mode) => (await open()).transaction('records', mode, { durability: 'strict' }).objectStore('records');
|
|
85
|
+
const compareAndSetMany = async (writes) => {
|
|
86
|
+
if (!writes.length || new Set(writes.map((w) => w.key)).size !== writes.length)
|
|
87
|
+
throw new TypeError('empty or duplicate CAS keys');
|
|
88
|
+
for (const write of writes)
|
|
89
|
+
checkNext(write.expected, write.next);
|
|
90
|
+
const s = await tx('readwrite');
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
let remaining = writes.length;
|
|
93
|
+
let matches = true;
|
|
94
|
+
let wrote = false;
|
|
95
|
+
for (const write of writes) {
|
|
96
|
+
const r = s.get(write.key);
|
|
97
|
+
r.onsuccess = () => {
|
|
98
|
+
try {
|
|
99
|
+
if (!holds(read(r.result), write.expected))
|
|
100
|
+
matches = false;
|
|
101
|
+
if (--remaining !== 0 || !matches)
|
|
102
|
+
return;
|
|
103
|
+
for (const item of writes) {
|
|
104
|
+
if (item.next === null)
|
|
105
|
+
s.delete(item.key);
|
|
106
|
+
else
|
|
107
|
+
s.put(JSON.stringify(item.next), item.key);
|
|
108
|
+
}
|
|
109
|
+
wrote = true;
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
reject(error);
|
|
113
|
+
s.transaction.abort();
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
s.transaction.oncomplete = () => resolve(wrote);
|
|
118
|
+
const failed = () => reject(s.transaction.error ?? new Error('the IndexedDB transaction was aborted'));
|
|
119
|
+
s.transaction.onerror = failed;
|
|
120
|
+
s.transaction.onabort = failed;
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
return {
|
|
124
|
+
load: async (key) => {
|
|
125
|
+
const s = await tx('readonly');
|
|
126
|
+
return new Promise((resolve, reject) => {
|
|
127
|
+
const r = s.get(key);
|
|
128
|
+
r.onsuccess = () => resolve(read(r.result));
|
|
129
|
+
r.onerror = () => reject(r.error);
|
|
130
|
+
});
|
|
131
|
+
},
|
|
132
|
+
compareAndSet: (key, expected, next) => compareAndSetMany([{ key, expected, next }]),
|
|
133
|
+
compareAndSetMany,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { type Purchase } from '../settle-near/purchase.js';
|
|
2
|
+
import type { Authorization } from './eip3009.js';
|
|
3
|
+
/** CAIP-2 id of Base mainnet. */
|
|
4
|
+
export declare const BASE_NETWORK = "eip155:8453";
|
|
5
|
+
/**
|
|
6
|
+
* The scheme of an Annon offer: plain EIP-3009 `TransferWithAuthorization` to the file's `pay_to`,
|
|
7
|
+
* under its own name so a client without Annon has no handler for it and signs nothing (checked
|
|
8
|
+
* against the official x402 client: it signs any `exact` offer with a random nonce, which could
|
|
9
|
+
* never release the key). The seller settles it through any standard facilitator as `exact`
|
|
10
|
+
* (`forFacilitator`): the scheme label is not part of what the buyer signs.
|
|
11
|
+
*/
|
|
12
|
+
export declare const ANNON_EXACT = "annon-exact";
|
|
13
|
+
export declare const ANNON_NEAR_USDC = "annon-near-usdc";
|
|
14
|
+
/** The x402 idempotency extension (x402-annon.md section 10). */
|
|
15
|
+
export declare const PAYMENT_IDENTIFIER = "payment-identifier";
|
|
16
|
+
/** As the x402 extension declares it, optional: the official `declarePaymentIdentifierExtension`. */
|
|
17
|
+
export declare const paymentIdentifierDeclaration: () => {
|
|
18
|
+
info: {
|
|
19
|
+
required: boolean;
|
|
20
|
+
};
|
|
21
|
+
schema: {
|
|
22
|
+
$schema: string;
|
|
23
|
+
type: string;
|
|
24
|
+
properties: {
|
|
25
|
+
required: {
|
|
26
|
+
type: string;
|
|
27
|
+
};
|
|
28
|
+
id: {
|
|
29
|
+
type: string;
|
|
30
|
+
minLength: number;
|
|
31
|
+
maxLength: number;
|
|
32
|
+
pattern: string;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
required: string[];
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
/** x402 builder codes (ERC-8021 attribution); the Annon SDK's own service code. */
|
|
39
|
+
export declare const BUILDER_CODE = "builder-code";
|
|
40
|
+
export declare const BUILDER_CODE_PATTERN: RegExp;
|
|
41
|
+
/** At most this many service codes echoed by a client: its 5 and the server's 5. */
|
|
42
|
+
export declare const MAX_ECHOED_SERVICE_CODES = 10;
|
|
43
|
+
/** Service codes as a list: the extension allows one code as a plain string. */
|
|
44
|
+
export declare const codes: (s: unknown) => unknown[];
|
|
45
|
+
/** x402 discovery (x402-annon.md section 11); never sent to a facilitator. */
|
|
46
|
+
export declare const BAZAAR = "bazaar";
|
|
47
|
+
/** A payment id the extension allows: 16 to 128 letters, digits, `-` and `_`. */
|
|
48
|
+
export declare const isPaymentId: (id: unknown) => id is string;
|
|
49
|
+
export interface Offer {
|
|
50
|
+
scheme: string;
|
|
51
|
+
network: string;
|
|
52
|
+
amount: string;
|
|
53
|
+
asset: string;
|
|
54
|
+
payTo: string;
|
|
55
|
+
maxTimeoutSeconds: number;
|
|
56
|
+
extra?: Record<string, unknown>;
|
|
57
|
+
}
|
|
58
|
+
/** `extensions.annon` as a server advertises it. */
|
|
59
|
+
interface AnnonExtension {
|
|
60
|
+
info: {
|
|
61
|
+
version?: unknown;
|
|
62
|
+
network?: unknown;
|
|
63
|
+
contract?: unknown;
|
|
64
|
+
fileId?: unknown;
|
|
65
|
+
header?: unknown;
|
|
66
|
+
download?: unknown;
|
|
67
|
+
public?: unknown;
|
|
68
|
+
};
|
|
69
|
+
schema?: unknown;
|
|
70
|
+
}
|
|
71
|
+
export interface PaymentRequired {
|
|
72
|
+
x402Version: 2;
|
|
73
|
+
resource?: unknown;
|
|
74
|
+
accepts: Offer[];
|
|
75
|
+
extensions?: {
|
|
76
|
+
annon?: AnnonExtension;
|
|
77
|
+
} & Record<string, unknown>;
|
|
78
|
+
}
|
|
79
|
+
/** The `PAYMENT-REQUIRED` header (base64 JSON), with only its shape checked. */
|
|
80
|
+
export declare function readPaymentRequired(header: string): PaymentRequired;
|
|
81
|
+
/**
|
|
82
|
+
* The Annon contract and file a payment request sells, from its `annon` extension. Only a
|
|
83
|
+
* contract in the required `trusted` list is ever returned.
|
|
84
|
+
*/
|
|
85
|
+
export declare function discover(pr: PaymentRequired, trusted: readonly string[]): {
|
|
86
|
+
contractId: string;
|
|
87
|
+
fileId: string;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* The `PAYMENT-SIGNATURE` header: the chosen offer echoed, the EIP-3009 authorization, and the
|
|
91
|
+
* `annon` extension echoed with the buyer's one-time key, from which the server recomputes the
|
|
92
|
+
* nonce before it settles. With `paymentId`, the advertised `payment-identifier` gets its id.
|
|
93
|
+
*/
|
|
94
|
+
export declare function paymentSignature(pr: PaymentRequired, offer: Offer, a: Authorization, signature: string, purchase: Purchase, paymentId?: string): string;
|
|
95
|
+
export {};
|
package/dist/pay/x402.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { buildPurchaseMessage, purchaseId } from '../settle-near/purchase.js';
|
|
2
|
+
/**
|
|
3
|
+
* The x402 v2 envelope of an Annon purchase (`specs/x402-annon.md`): reading the `PAYMENT-REQUIRED`
|
|
4
|
+
* header, finding the file it sells, and building the `PAYMENT-SIGNATURE` header. Nothing read here
|
|
5
|
+
* is trusted: `preflight` compares it with the contract.
|
|
6
|
+
*/
|
|
7
|
+
import { fail } from './base.js';
|
|
8
|
+
/** CAIP-2 id of Base mainnet. */
|
|
9
|
+
export const BASE_NETWORK = 'eip155:8453';
|
|
10
|
+
/**
|
|
11
|
+
* The scheme of an Annon offer: plain EIP-3009 `TransferWithAuthorization` to the file's `pay_to`,
|
|
12
|
+
* under its own name so a client without Annon has no handler for it and signs nothing (checked
|
|
13
|
+
* against the official x402 client: it signs any `exact` offer with a random nonce, which could
|
|
14
|
+
* never release the key). The seller settles it through any standard facilitator as `exact`
|
|
15
|
+
* (`forFacilitator`): the scheme label is not part of what the buyer signs.
|
|
16
|
+
*/
|
|
17
|
+
export const ANNON_EXACT = 'annon-exact';
|
|
18
|
+
export const ANNON_NEAR_USDC = 'annon-near-usdc';
|
|
19
|
+
/** The x402 idempotency extension (x402-annon.md section 10). */
|
|
20
|
+
export const PAYMENT_IDENTIFIER = 'payment-identifier';
|
|
21
|
+
/** As the x402 extension declares it, optional: the official `declarePaymentIdentifierExtension`. */
|
|
22
|
+
export const paymentIdentifierDeclaration = () => ({
|
|
23
|
+
info: { required: false },
|
|
24
|
+
schema: {
|
|
25
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
26
|
+
type: 'object',
|
|
27
|
+
properties: {
|
|
28
|
+
required: { type: 'boolean' },
|
|
29
|
+
id: { type: 'string', minLength: 16, maxLength: 128, pattern: '^[a-zA-Z0-9_-]+$' },
|
|
30
|
+
},
|
|
31
|
+
required: ['required'],
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
/** x402 builder codes (ERC-8021 attribution); the Annon SDK's own service code. */
|
|
35
|
+
export const BUILDER_CODE = 'builder-code';
|
|
36
|
+
const ANNON_SERVICE_CODE = 'bc_lq0mq0v9';
|
|
37
|
+
export const BUILDER_CODE_PATTERN = /^[a-z0-9_]{1,32}$/;
|
|
38
|
+
/** At most this many service codes echoed by a client: its 5 and the server's 5. */
|
|
39
|
+
export const MAX_ECHOED_SERVICE_CODES = 10;
|
|
40
|
+
/** Service codes as a list: the extension allows one code as a plain string. */
|
|
41
|
+
export const codes = (s) => (s === undefined ? [] : Array.isArray(s) ? s : [s]);
|
|
42
|
+
/** x402 discovery (x402-annon.md section 11); never sent to a facilitator. */
|
|
43
|
+
export const BAZAAR = 'bazaar';
|
|
44
|
+
/** A payment id the extension allows: 16 to 128 letters, digits, `-` and `_`. */
|
|
45
|
+
export const isPaymentId = (id) => typeof id === 'string' && /^[a-zA-Z0-9_-]{16,128}$/.test(id);
|
|
46
|
+
const utf8 = new TextEncoder();
|
|
47
|
+
const b64encode = (s) => {
|
|
48
|
+
let bin = '';
|
|
49
|
+
for (const b of utf8.encode(s))
|
|
50
|
+
bin += String.fromCharCode(b);
|
|
51
|
+
return btoa(bin);
|
|
52
|
+
};
|
|
53
|
+
const b64decode = (s) => new TextDecoder('utf-8', { fatal: true }).decode(Uint8Array.from(atob(s), (c) => c.charCodeAt(0)));
|
|
54
|
+
/** The `PAYMENT-REQUIRED` header (base64 JSON), with only its shape checked. */
|
|
55
|
+
export function readPaymentRequired(header) {
|
|
56
|
+
let v;
|
|
57
|
+
try {
|
|
58
|
+
v = JSON.parse(b64decode(header.trim()));
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return fail('bad_requirements', 'PAYMENT-REQUIRED is not base64 JSON');
|
|
62
|
+
}
|
|
63
|
+
if (v?.x402Version !== 2 || !Array.isArray(v.accepts)) {
|
|
64
|
+
return fail('bad_requirements', 'not an x402 v2 payment request');
|
|
65
|
+
}
|
|
66
|
+
return v;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The Annon contract and file a payment request sells, from its `annon` extension. Only a
|
|
70
|
+
* contract in the required `trusted` list is ever returned.
|
|
71
|
+
*/
|
|
72
|
+
export function discover(pr, trusted) {
|
|
73
|
+
const info = pr.extensions?.annon?.info;
|
|
74
|
+
if (!info)
|
|
75
|
+
return fail('file_mismatch', 'the payment request names no Annon file');
|
|
76
|
+
if (info.version !== 3 || info.network !== 'near:mainnet') {
|
|
77
|
+
fail('file_mismatch', 'the Annon extension is not version 3 on NEAR mainnet');
|
|
78
|
+
}
|
|
79
|
+
if (typeof info.contract !== 'string' || !trusted.includes(info.contract)) {
|
|
80
|
+
fail('untrusted_contract', `the Annon contract ${String(info.contract)} is not one this client trusts`);
|
|
81
|
+
}
|
|
82
|
+
if (typeof info.fileId !== 'string' || !/^0x[0-9a-f]{64}$/.test(info.fileId)) {
|
|
83
|
+
fail('file_mismatch', 'the Annon extension has no valid fileId');
|
|
84
|
+
}
|
|
85
|
+
return { contractId: info.contract, fileId: info.fileId };
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The `PAYMENT-SIGNATURE` header: the chosen offer echoed, the EIP-3009 authorization, and the
|
|
89
|
+
* `annon` extension echoed with the buyer's one-time key, from which the server recomputes the
|
|
90
|
+
* nonce before it settles. With `paymentId`, the advertised `payment-identifier` gets its id.
|
|
91
|
+
*/
|
|
92
|
+
export function paymentSignature(pr, offer, a, signature, purchase, paymentId) {
|
|
93
|
+
const pid = pr.extensions?.[PAYMENT_IDENTIFIER];
|
|
94
|
+
// As x402's client merge: the Annon SDK's code first, then the declared codes, no duplicates.
|
|
95
|
+
const bc = pr.extensions?.[BUILDER_CODE];
|
|
96
|
+
const declaredS = codes(bc?.info?.s);
|
|
97
|
+
const builderCode = {
|
|
98
|
+
...(bc ?? {}),
|
|
99
|
+
info: { ...(bc?.info ?? {}), s: [...new Set([ANNON_SERVICE_CODE, ...declaredS])] },
|
|
100
|
+
};
|
|
101
|
+
const advertised = pr.extensions?.annon;
|
|
102
|
+
buildPurchaseMessage(purchase, { baseUsdc: offer.asset });
|
|
103
|
+
const info = {
|
|
104
|
+
...(advertised?.info ?? {}),
|
|
105
|
+
version: 3,
|
|
106
|
+
network: 'near:mainnet',
|
|
107
|
+
contract: purchase.contract,
|
|
108
|
+
fileId: purchase.file_id,
|
|
109
|
+
purchase: buildPurchaseMessage(purchase, { baseUsdc: offer.asset }),
|
|
110
|
+
purchase_id: purchaseId(purchase, { baseUsdc: offer.asset }),
|
|
111
|
+
};
|
|
112
|
+
return b64encode(JSON.stringify({
|
|
113
|
+
x402Version: 2,
|
|
114
|
+
...(pr.resource === undefined ? {} : { resource: pr.resource }),
|
|
115
|
+
accepted: offer,
|
|
116
|
+
payload: {
|
|
117
|
+
signature,
|
|
118
|
+
authorization: {
|
|
119
|
+
from: a.from,
|
|
120
|
+
to: a.to,
|
|
121
|
+
value: a.value.toString(),
|
|
122
|
+
validAfter: a.validAfter.toString(),
|
|
123
|
+
validBefore: a.validBefore.toString(),
|
|
124
|
+
nonce: a.nonce,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
extensions: {
|
|
128
|
+
...(pr.extensions ?? {}),
|
|
129
|
+
annon: { ...(advertised ?? {}), info },
|
|
130
|
+
...(pid && paymentId
|
|
131
|
+
? { [PAYMENT_IDENTIFIER]: { ...pid, info: { ...pid.info, id: paymentId } } }
|
|
132
|
+
: {}),
|
|
133
|
+
[BUILDER_CODE]: builderCode,
|
|
134
|
+
},
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface AdmissionOptions {
|
|
2
|
+
admissionFloor?: bigint;
|
|
3
|
+
admissionBalance?: () => Promise<bigint>;
|
|
4
|
+
alert?: (type: string) => void;
|
|
5
|
+
}
|
|
6
|
+
/** Final liquid account funds, excluding required account storage stake. */
|
|
7
|
+
export declare function admissionBalance(nearRpc: string, accountId: string): Promise<bigint>;
|
|
8
|
+
export declare function checkAdmissionFloor(method: string, o: AdmissionOptions): Promise<void>;
|