@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,173 @@
|
|
|
1
|
+
/** Contract calls for the SDK: a selected NEAR wallet signs, otherwise the relayer submits. */
|
|
2
|
+
import { Annon } from '../api/annon.js';
|
|
3
|
+
import { secureUrl } from '../download/download.js';
|
|
4
|
+
import { AnnonError, isAnnonErrorCode, isTransient } from '../errors.js';
|
|
5
|
+
import { isStorageRequired, txOutcome } from '../near/rpc.js';
|
|
6
|
+
import { parseRetryAfterMs } from '../net/retry.js';
|
|
7
|
+
import { protocolFeeAboveMax } from '../relay/fees.js';
|
|
8
|
+
import { TransactionUncertain } from '../wallet/uncertain.js';
|
|
9
|
+
import { backupPath, fail } from './adapter-checks.js';
|
|
10
|
+
import { amount } from './amounts.js';
|
|
11
|
+
import { AdapterError, publicError } from './errors.js';
|
|
12
|
+
import { writeBackup } from './io.js';
|
|
13
|
+
import { requestOptions, response } from './offers.js';
|
|
14
|
+
/**
|
|
15
|
+
* Sends one contract call and waits for its final outcome. Relayer refusals keep their public
|
|
16
|
+
* code; admission limits, Base settlement and pending key releases are retried with fixed waits.
|
|
17
|
+
*/
|
|
18
|
+
async function relaySend(ctx, method, args, owner) {
|
|
19
|
+
const { c, d, fetcher, o, wait } = ctx;
|
|
20
|
+
if (method === 'unlock' && !c.baseRpc)
|
|
21
|
+
return fail('base_rpc_required', 'Configure baseRpc before resuming a Base purchase.', 2);
|
|
22
|
+
// The relayer gets the full request; the contract call, and so the outcome check, never
|
|
23
|
+
// carries the relayer-only fields.
|
|
24
|
+
const { baseSplit: _baseSplit, hostingReservation: _hostingReservation, ...envelope } = args;
|
|
25
|
+
const funding = envelope.args;
|
|
26
|
+
const wireArgs = method === 'fund_file_storage'
|
|
27
|
+
? { file_id: funding.file_id, amount: funding.amount }
|
|
28
|
+
: envelope;
|
|
29
|
+
const signer = owner ?? (ctx.selected?.identity.kind === 'near' ? ctx.selected : undefined);
|
|
30
|
+
let submitted;
|
|
31
|
+
const call = async () => {
|
|
32
|
+
if (signer)
|
|
33
|
+
return signer.call({ receiverId: c.contractId, method, args: wireArgs });
|
|
34
|
+
if (!c.relayer)
|
|
35
|
+
return fail('transport_required', 'Configure a relayer URL or select a NEAR wallet for transaction calls.', 2);
|
|
36
|
+
if (!submitted) {
|
|
37
|
+
const r = await fetcher(new URL('/v1/relay', secureUrl(c.relayer)), {
|
|
38
|
+
...requestOptions,
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: { 'content-type': 'application/json' },
|
|
41
|
+
body: JSON.stringify(method === 'claim_key' ? { method, args } : { method, ...args }),
|
|
42
|
+
signal: AbortSignal.timeout(60000),
|
|
43
|
+
});
|
|
44
|
+
if (![200, 202].includes(r.status)) {
|
|
45
|
+
const body = (await r.json().catch(() => null));
|
|
46
|
+
const fallback = r.status === 425
|
|
47
|
+
? 'settlement_pending'
|
|
48
|
+
: r.status === 429
|
|
49
|
+
? 'relayer_rate_limited'
|
|
50
|
+
: r.status === 503
|
|
51
|
+
? 'relayer_unavailable'
|
|
52
|
+
: 'operation_failed';
|
|
53
|
+
const code = isAnnonErrorCode(body?.error) ? body.error : fallback;
|
|
54
|
+
const delay = parseRetryAfterMs(r.headers.get('Retry-After'));
|
|
55
|
+
const safe = publicError({
|
|
56
|
+
...(body && isAnnonErrorCode(body.error) ? body : {}),
|
|
57
|
+
code,
|
|
58
|
+
...(delay !== undefined ? { retryAfterSeconds: Math.ceil(delay / 1000) } : {}),
|
|
59
|
+
});
|
|
60
|
+
const error = Object.assign(new AdapterError(safe.code, safe.message, safe.exitCode), safe);
|
|
61
|
+
if (code === 'budget')
|
|
62
|
+
throw error;
|
|
63
|
+
if (code === 'settlement_pending')
|
|
64
|
+
throw Object.assign(error, { safetyDelayMs: (safe.retryAfterSeconds ?? 30) * 1000 });
|
|
65
|
+
const waitMs = delay ?? (safe.retryAfterSeconds === undefined ? 5000 : safe.retryAfterSeconds * 1000);
|
|
66
|
+
if ([429, 503].includes(r.status) && waitMs <= 60000)
|
|
67
|
+
throw Object.assign(error, { retryAfterMs: waitMs });
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
const body = (await r.json());
|
|
71
|
+
if (r.status === 202 && body.pending === true)
|
|
72
|
+
throw new AdapterError('in_flight', 'Relayer operation is pending.', 4);
|
|
73
|
+
if (typeof body.txHash !== 'string' ||
|
|
74
|
+
!body.txHash ||
|
|
75
|
+
typeof body.signer !== 'string' ||
|
|
76
|
+
!body.signer)
|
|
77
|
+
throw new Error('invalid relayer transaction response');
|
|
78
|
+
submitted = { txHash: body.txHash, signer: body.signer };
|
|
79
|
+
}
|
|
80
|
+
const tx = submitted;
|
|
81
|
+
const result = await txOutcome(c.nearRpc, tx.txHash, tx.signer, c.contractId, 'FINAL', {
|
|
82
|
+
method,
|
|
83
|
+
args: wireArgs,
|
|
84
|
+
});
|
|
85
|
+
if (result.state === 'released')
|
|
86
|
+
return result.answer;
|
|
87
|
+
if (result.state === 'done')
|
|
88
|
+
return result.value;
|
|
89
|
+
if ('reason' in result && isStorageRequired(result.reason))
|
|
90
|
+
throw new AdapterError('storage_required', 'Prepay Annon storage with the SDK storageDeposit helper, then resume the saved operation.');
|
|
91
|
+
throw new Error(`transaction ${result.state}`);
|
|
92
|
+
};
|
|
93
|
+
// Timeouts repeat only idempotent releases; explicit admission refusals have their own cap.
|
|
94
|
+
let timeouts = 0;
|
|
95
|
+
let pending = 0;
|
|
96
|
+
let admissions = 0;
|
|
97
|
+
let safetyWaits = 0;
|
|
98
|
+
for (;;) {
|
|
99
|
+
try {
|
|
100
|
+
return await call();
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
if (o.command === 'share' && submitted) {
|
|
104
|
+
if (error instanceof AnnonError && !isTransient(error))
|
|
105
|
+
throw Object.assign(error, { txHash: submitted.txHash });
|
|
106
|
+
throw new TransactionUncertain(submitted.txHash);
|
|
107
|
+
}
|
|
108
|
+
if (error instanceof AdapterError && 'retryAfterMs' in error) {
|
|
109
|
+
if (admissions++ >= 3)
|
|
110
|
+
throw error;
|
|
111
|
+
await wait(error.retryAfterMs);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (method === 'unlock' &&
|
|
115
|
+
error instanceof AdapterError &&
|
|
116
|
+
error.code === 'settlement_pending') {
|
|
117
|
+
if (o.command === 'status')
|
|
118
|
+
throw error;
|
|
119
|
+
if (o.command !== 'open' || d.agent || o.json)
|
|
120
|
+
throw error;
|
|
121
|
+
const planned = [10000, 20000, 30000, 40000, 60000][safetyWaits++];
|
|
122
|
+
const minimum = error.safetyDelayMs ?? 0;
|
|
123
|
+
if (planned === undefined || minimum > 60000)
|
|
124
|
+
throw error;
|
|
125
|
+
const delay = Math.max(planned, Number.isFinite(minimum) ? minimum : 0);
|
|
126
|
+
d.notify?.(`Base settlement pending; retrying unlock in ${delay / 1000} seconds.`);
|
|
127
|
+
await wait(delay);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const inFlight = error instanceof AdapterError && ['in_flight', 'duplicate'].includes(error.code);
|
|
131
|
+
const timeout = isTransient(error) || (error instanceof Error && error.name === 'TimeoutError');
|
|
132
|
+
if (!['unlock', 'reopen', 'claim_key'].includes(method) ||
|
|
133
|
+
(!inFlight && !timeout) ||
|
|
134
|
+
(inFlight ? pending++ >= 36 : timeouts++ >= 2))
|
|
135
|
+
throw error;
|
|
136
|
+
if (!o.json)
|
|
137
|
+
d.notify?.('Key release pending; retrying the saved request.');
|
|
138
|
+
await wait(inFlight ? 5000 : 1000 * 2 ** (timeouts - 1));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** The SDK for this run, wired to the relay transport and the relayer's sponsorship policy. */
|
|
143
|
+
function createSdk(ctx, rawStore, secret) {
|
|
144
|
+
const { c, d, fetcher, o } = ctx;
|
|
145
|
+
const send = (method, args, owner) => relaySend(ctx, method, args, owner);
|
|
146
|
+
const sdk = (d.sdk ?? ((options) => new Annon(options)))({
|
|
147
|
+
...c,
|
|
148
|
+
rawStore,
|
|
149
|
+
fetch: fetcher,
|
|
150
|
+
backupSecret: secret,
|
|
151
|
+
send,
|
|
152
|
+
...(c.relayer
|
|
153
|
+
? {
|
|
154
|
+
sponsorship: async () => {
|
|
155
|
+
const reply = await response(new URL('/v1/capabilities', c.relayer).href, fetcher);
|
|
156
|
+
if (!reply.ok)
|
|
157
|
+
throw new Error('relayer capabilities unavailable');
|
|
158
|
+
const cap = (await reply.json());
|
|
159
|
+
if (cap.contract !== c.contractId || cap.disabled || !cap.fee)
|
|
160
|
+
throw new Error('invalid relayer sponsorship policy');
|
|
161
|
+
if ((d.agent || o.json) &&
|
|
162
|
+
o.price &&
|
|
163
|
+
protocolFeeAboveMax(amount(o.price), cap.fee, c.maxProtocolFee))
|
|
164
|
+
return fail('protocol_fee_above_max', 'The advertised protocol fee exceeds maxProtocolFee. Review the fee in human configuration.', 2);
|
|
165
|
+
return { fee: cap.fee };
|
|
166
|
+
},
|
|
167
|
+
}
|
|
168
|
+
: {}),
|
|
169
|
+
writeBackup: (id, encrypted) => writeBackup(backupPath(c, id), encrypted),
|
|
170
|
+
});
|
|
171
|
+
return sdk;
|
|
172
|
+
}
|
|
173
|
+
export { createSdk };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { secureUrl } from '../download/download.js';
|
|
2
|
+
import { sameResource } from '../download/proof.js';
|
|
3
|
+
/** Absent means open HTTPS policy. An explicit empty list denies every resource. */
|
|
4
|
+
export function resourceAllowed(rules, resource) {
|
|
5
|
+
try {
|
|
6
|
+
const url = secureUrl(resource);
|
|
7
|
+
if (/%2f|%5c/i.test(resource))
|
|
8
|
+
return false;
|
|
9
|
+
if (url.username || url.password)
|
|
10
|
+
return false;
|
|
11
|
+
if (rules === undefined)
|
|
12
|
+
return true;
|
|
13
|
+
return rules.some((rule) => {
|
|
14
|
+
if (!rule.endsWith('/*'))
|
|
15
|
+
return sameResource(rule, resource);
|
|
16
|
+
const prefix = secureUrl(rule.slice(0, -1));
|
|
17
|
+
return (!prefix.search &&
|
|
18
|
+
!prefix.hash &&
|
|
19
|
+
prefix.origin === url.origin &&
|
|
20
|
+
url.pathname.startsWith(prefix.pathname));
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type AnnonErrorCode } from '../errors.js';
|
|
2
|
+
interface Context {
|
|
3
|
+
stateDir: string;
|
|
4
|
+
network: string;
|
|
5
|
+
contractId: string;
|
|
6
|
+
purchaseId: string;
|
|
7
|
+
host?: string;
|
|
8
|
+
fileId?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Host and deployment are operator-checked; grants and URL paths never reset a purchase wait. */
|
|
11
|
+
export declare function retryGate(c: Context, now?: () => number): {
|
|
12
|
+
check(): Promise<void>;
|
|
13
|
+
defer(code: AnnonErrorCode, seconds: number): Promise<void>;
|
|
14
|
+
};
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { mkdir } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { isAnnonErrorCode } from '../errors.js';
|
|
4
|
+
import { fileStore } from '../pay/store-node.js';
|
|
5
|
+
import { AdapterError } from './errors.js';
|
|
6
|
+
/** Host and deployment are operator-checked; grants and URL paths never reset a purchase wait. */
|
|
7
|
+
export function retryGate(c, now = () => Date.now() / 1000) {
|
|
8
|
+
const dir = join(c.stateDir, 'retry-gates');
|
|
9
|
+
const store = fileStore(dir);
|
|
10
|
+
const id = JSON.stringify([
|
|
11
|
+
c.network,
|
|
12
|
+
c.contractId,
|
|
13
|
+
c.host ?? null,
|
|
14
|
+
c.purchaseId,
|
|
15
|
+
c.fileId ?? null,
|
|
16
|
+
]);
|
|
17
|
+
return {
|
|
18
|
+
async check() {
|
|
19
|
+
const saved = await store.load(id);
|
|
20
|
+
if (!saved)
|
|
21
|
+
return;
|
|
22
|
+
if (!isAnnonErrorCode(saved.code) ||
|
|
23
|
+
!Number.isFinite(saved.nextAllowedAt) ||
|
|
24
|
+
saved.nextAllowedAt < 0)
|
|
25
|
+
throw new AdapterError('invalid_record', 'Invalid saved retry gate.', 2);
|
|
26
|
+
const remaining = Math.ceil(saved.nextAllowedAt - now());
|
|
27
|
+
if (remaining > 0)
|
|
28
|
+
throw Object.assign(new AdapterError(saved.code, 'Wait before retrying this saved purchase.', 4), {
|
|
29
|
+
retryAfterSeconds: remaining,
|
|
30
|
+
purchaseId: c.purchaseId,
|
|
31
|
+
...(c.fileId ? { fileId: c.fileId } : {}),
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
async defer(code, seconds) {
|
|
35
|
+
if (!Number.isFinite(seconds) || seconds < 0 || !Number.isSafeInteger(Math.ceil(seconds)))
|
|
36
|
+
return;
|
|
37
|
+
const nextAllowedAt = now() + Math.ceil(seconds);
|
|
38
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
39
|
+
for (;;) {
|
|
40
|
+
const before = await store.load(id);
|
|
41
|
+
if (before && before.nextAllowedAt >= nextAllowedAt)
|
|
42
|
+
return;
|
|
43
|
+
if (await store.compareAndSet(id, before, {
|
|
44
|
+
id,
|
|
45
|
+
revision: (before?.revision ?? -1) + 1,
|
|
46
|
+
nextAllowedAt,
|
|
47
|
+
code,
|
|
48
|
+
}))
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CommandSession } from './command-context.js';
|
|
2
|
+
import type { selectedShopRules } from './shops.js';
|
|
3
|
+
/**
|
|
4
|
+
* `share`: hosts the file with the selected shop and registers it; in a human session the seller
|
|
5
|
+
* approves the sale terms first. `--resume` continues a saved hosted sale.
|
|
6
|
+
*/
|
|
7
|
+
declare function share(ctx: CommandSession, hostedOrigin: string | undefined, shopRules: Awaited<ReturnType<typeof selectedShopRules>>): Promise<Record<string, unknown>>;
|
|
8
|
+
/** `sell`: writes the encrypted .pay2 file and registers its split; `--resume` continues one. */
|
|
9
|
+
declare function sell(ctx: CommandSession): Promise<Record<string, unknown>>;
|
|
10
|
+
/** `stop`: the registered seller stops new sales of a file. */
|
|
11
|
+
declare function stop(ctx: CommandSession): Promise<Record<string, unknown>>;
|
|
12
|
+
export { sell, share, stop };
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/** Seller commands: `share` through a hosted shop, `sell` a local file, `stop` a listing. */
|
|
2
|
+
import { basename, join } from 'node:path';
|
|
3
|
+
import { identityJson } from '../settle-near/identity.js';
|
|
4
|
+
import { fail, isId } from './adapter-checks.js';
|
|
5
|
+
import { amount, nearAmount } from './amounts.js';
|
|
6
|
+
import { AdapterError, usage } from './errors.js';
|
|
7
|
+
import { fileSource, newOutput, withSpool, writeStream } from './io.js';
|
|
8
|
+
import { identity } from './wallets.js';
|
|
9
|
+
/**
|
|
10
|
+
* `share`: hosts the file with the selected shop and registers it; in a human session the seller
|
|
11
|
+
* approves the sale terms first. `--resume` continues a saved hosted sale.
|
|
12
|
+
*/
|
|
13
|
+
async function share(ctx, hostedOrigin, shopRules) {
|
|
14
|
+
const { c, d, o, sdk, wallet } = ctx;
|
|
15
|
+
if (!o.target || !hostedOrigin || !sdk.share || !sdk.resumeShare)
|
|
16
|
+
throw usage();
|
|
17
|
+
const seller = await wallet();
|
|
18
|
+
const hosting = {
|
|
19
|
+
origin: hostedOrigin,
|
|
20
|
+
...(seller.signUpload ? { signUpload: seller.signUpload } : {}),
|
|
21
|
+
spool: (stream, use) => withSpool(join(c.stateDir, 'hosted'), stream, use),
|
|
22
|
+
};
|
|
23
|
+
const data = await fileSource(o.target);
|
|
24
|
+
if (o.resume) {
|
|
25
|
+
if (!isId(o.resume) ||
|
|
26
|
+
[o.price, o.title, o.description, o.storageBudget, o.listingStorage].some((v) => v !== undefined))
|
|
27
|
+
throw usage();
|
|
28
|
+
return {
|
|
29
|
+
...(await sdk.resumeShare({ fileId: o.resume, data, seller, hosting })),
|
|
30
|
+
...shopRules,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (!o.price)
|
|
34
|
+
throw usage();
|
|
35
|
+
const near = seller.identity.kind === 'near';
|
|
36
|
+
if (near &&
|
|
37
|
+
(d.agent || o.json) &&
|
|
38
|
+
(!(d.agent ? c.listingStorage : (o.listingStorage ?? c.listingStorage)) ||
|
|
39
|
+
!(d.agent ? c.storageBudget : (o.storageBudget ?? c.storageBudget))))
|
|
40
|
+
throw new AdapterError('storage_required', 'Configure listingStorage and storageBudget, or provide --listing-storage and --storage-budget in a human CLI session, before noninteractive NEAR sharing.', 2);
|
|
41
|
+
const listingStorage = near
|
|
42
|
+
? nearAmount(d.agent ? (c.listingStorage ?? '0.01') : (o.listingStorage ?? c.listingStorage ?? '0.01'))
|
|
43
|
+
: undefined;
|
|
44
|
+
const storageBudget = near
|
|
45
|
+
? nearAmount(d.agent ? (c.storageBudget ?? '0.05') : (o.storageBudget ?? c.storageBudget ?? '0.05'))
|
|
46
|
+
: undefined;
|
|
47
|
+
if (!near && (o.listingStorage || o.storageBudget))
|
|
48
|
+
throw usage();
|
|
49
|
+
return {
|
|
50
|
+
...shopRules,
|
|
51
|
+
...(await sdk.share({
|
|
52
|
+
data,
|
|
53
|
+
name: basename(o.target),
|
|
54
|
+
mime: 'application/octet-stream',
|
|
55
|
+
seller,
|
|
56
|
+
price: amount(o.price),
|
|
57
|
+
hosting,
|
|
58
|
+
...(o.title !== undefined ? { title: o.title } : {}),
|
|
59
|
+
...(o.description !== undefined ? { description: o.description } : {}),
|
|
60
|
+
...(listingStorage !== undefined ? { listingStorage } : {}),
|
|
61
|
+
...(storageBudget !== undefined ? { storageBudget } : {}),
|
|
62
|
+
onSplit: async (split, fee, aboveMaximum) => {
|
|
63
|
+
if (aboveMaximum && (d.agent || o.json))
|
|
64
|
+
throw new AdapterError('protocol_fee_above_max', 'Review maxProtocolFee in human configuration.', 2);
|
|
65
|
+
const terms = {
|
|
66
|
+
seller: seller.identity,
|
|
67
|
+
host: hostedOrigin,
|
|
68
|
+
contract: c.contractId,
|
|
69
|
+
price: split.price,
|
|
70
|
+
split,
|
|
71
|
+
fee: fee ?? null,
|
|
72
|
+
listingStorage: listingStorage?.toString() ?? null,
|
|
73
|
+
purchaseStorage: storageBudget?.toString() ?? null,
|
|
74
|
+
registerDeposit: '0',
|
|
75
|
+
};
|
|
76
|
+
d.paymentTerms?.(terms);
|
|
77
|
+
if (!d.agent &&
|
|
78
|
+
!o.json &&
|
|
79
|
+
!(await d.prompt?.(`${JSON.stringify(terms)}\nType yes to share: `)))
|
|
80
|
+
throw new AdapterError('sale_not_approved', 'Sale terms were not approved.', 3);
|
|
81
|
+
},
|
|
82
|
+
// The configured secret is separate from the durable backup files.
|
|
83
|
+
confirmBackup: async () => { },
|
|
84
|
+
})),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/** `sell`: writes the encrypted .pay2 file and registers its split; `--resume` continues one. */
|
|
88
|
+
async function sell(ctx) {
|
|
89
|
+
const { c, d, o, sdk, wallet } = ctx;
|
|
90
|
+
if (!o.target)
|
|
91
|
+
throw usage();
|
|
92
|
+
const seller = await wallet();
|
|
93
|
+
const output = o.output ?? `${o.target}.pay2`;
|
|
94
|
+
await newOutput(output);
|
|
95
|
+
const publish = async (file) => {
|
|
96
|
+
await writeStream(output, file.file);
|
|
97
|
+
};
|
|
98
|
+
if (o.resume) {
|
|
99
|
+
if (!isId(o.resume) || o.price || o.split || o.base || o.moderator || o.storageBudget)
|
|
100
|
+
throw usage();
|
|
101
|
+
const result = await sdk.resumeSell({
|
|
102
|
+
fileId: o.resume,
|
|
103
|
+
data: await fileSource(o.target),
|
|
104
|
+
seller,
|
|
105
|
+
publish,
|
|
106
|
+
});
|
|
107
|
+
return {
|
|
108
|
+
fileId: result.terms.file_id,
|
|
109
|
+
file: output,
|
|
110
|
+
terms: result.terms,
|
|
111
|
+
...(result.baseSplit ? { baseSplit: result.baseSplit } : {}),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (!o.price || !o.split)
|
|
115
|
+
throw usage();
|
|
116
|
+
if (o.base && !c.baseRpc)
|
|
117
|
+
return fail('base_rpc_required', 'Configure baseRpc before creating a Base listing.', 2);
|
|
118
|
+
const recipients = o.split.split(',').map((entry) => {
|
|
119
|
+
const at = entry.lastIndexOf(':');
|
|
120
|
+
return { identity: identity(entry.slice(0, at)), bps: Number(entry.slice(at + 1)) };
|
|
121
|
+
});
|
|
122
|
+
if (!recipients.length ||
|
|
123
|
+
recipients.length > 8 ||
|
|
124
|
+
recipients.some((r) => !Number.isInteger(r.bps) || r.bps <= 0 || r.bps > 10000) ||
|
|
125
|
+
recipients.reduce((n, r) => n + r.bps, 0) !== 10000 ||
|
|
126
|
+
new Set(recipients.map((r) => identityJson(r.identity))).size !== recipients.length)
|
|
127
|
+
throw usage();
|
|
128
|
+
const remainder = recipients[0]?.identity;
|
|
129
|
+
if (!remainder)
|
|
130
|
+
throw usage();
|
|
131
|
+
if (o.base && (!seller.sendBase || recipients.some((r) => r.identity.kind !== 'evm')))
|
|
132
|
+
return fail('base_wallet_required', 'Base listings require an EVM wallet and EVM split recipients.', 2);
|
|
133
|
+
const storageBudget = d.agent ? c.storageBudget : (o.storageBudget ?? c.storageBudget);
|
|
134
|
+
const selfNear = seller.identity.kind === 'near';
|
|
135
|
+
if (o.storageBudget && !selfNear)
|
|
136
|
+
return fail('invalid_arguments', '--storage-budget requires a self-registered NEAR sell.', 2);
|
|
137
|
+
if (selfNear && !storageBudget)
|
|
138
|
+
d.notify?.('This self-registered file has no purchase storage budget. Fund it before NEAR or 1Click purchases.');
|
|
139
|
+
const result = await sdk.sell({
|
|
140
|
+
data: await fileSource(o.target),
|
|
141
|
+
name: basename(o.target),
|
|
142
|
+
mime: 'application/octet-stream',
|
|
143
|
+
seller,
|
|
144
|
+
near: { price: amount(o.price), recipients, remainder },
|
|
145
|
+
...((!c.relayer || seller.identity.kind === 'near') &&
|
|
146
|
+
(d.agent ? c.storageBudget : (o.storageBudget ?? c.storageBudget))
|
|
147
|
+
? {
|
|
148
|
+
storageBudget: nearAmount((d.agent ? c.storageBudget : (o.storageBudget ?? c.storageBudget))),
|
|
149
|
+
}
|
|
150
|
+
: {}),
|
|
151
|
+
onSplit: async (split, fee, aboveMaximum) => {
|
|
152
|
+
if (aboveMaximum && (d.agent || o.json))
|
|
153
|
+
return fail('protocol_fee_above_max', 'The advertised protocol fee exceeds maxProtocolFee. Review the fee in human configuration.', 2);
|
|
154
|
+
if (!c.relayer || seller.identity.kind === 'near')
|
|
155
|
+
return;
|
|
156
|
+
const description = [
|
|
157
|
+
'Sponsored sale split:',
|
|
158
|
+
...(fee
|
|
159
|
+
? [
|
|
160
|
+
`Protocol fee: ${fee.fixed} USDC base units + ${fee.bps} bps.`,
|
|
161
|
+
...(aboveMaximum ? ['This exceeds maxProtocolFee.'] : []),
|
|
162
|
+
]
|
|
163
|
+
: []),
|
|
164
|
+
...split.recipients.map((r, i) => `${identityJson(r.identity)}: ${(r.bps / 100).toFixed(2)}%${i === split.remainder ? ' (rounding remainder)' : ''}`),
|
|
165
|
+
].join('\n');
|
|
166
|
+
if (!d.agent && !o.json) {
|
|
167
|
+
d.notify?.(description);
|
|
168
|
+
if (!(await d.prompt?.(`${description}\nType yes to register: `)))
|
|
169
|
+
return fail('sale_not_approved', 'Sale split was not approved.', 3);
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
...(o.base
|
|
173
|
+
? {
|
|
174
|
+
base: {
|
|
175
|
+
price: amount(o.price),
|
|
176
|
+
sendTransaction: async (call) => {
|
|
177
|
+
await seller.sendBase?.(call, c.baseRpc);
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
}
|
|
181
|
+
: {}),
|
|
182
|
+
...(o.moderator ? { moderator: o.moderator } : {}),
|
|
183
|
+
publish,
|
|
184
|
+
});
|
|
185
|
+
return {
|
|
186
|
+
fileId: result.terms.file_id,
|
|
187
|
+
file: output,
|
|
188
|
+
terms: result.terms,
|
|
189
|
+
...(result.baseSplit ? { baseSplit: result.baseSplit } : {}),
|
|
190
|
+
...(selfNear && !storageBudget
|
|
191
|
+
? {
|
|
192
|
+
warnings: [
|
|
193
|
+
'This file has no purchase storage budget. Fund it before NEAR or 1Click purchases.',
|
|
194
|
+
],
|
|
195
|
+
}
|
|
196
|
+
: {}),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/** `stop`: the registered seller stops new sales of a file. */
|
|
200
|
+
async function stop(ctx) {
|
|
201
|
+
const { o, sdk, wallet } = ctx;
|
|
202
|
+
if (!o.target || !isId(o.target))
|
|
203
|
+
throw usage();
|
|
204
|
+
await sdk.stop({ fileId: o.target, seller: await wallet() });
|
|
205
|
+
return { fileId: o.target, stopped: true };
|
|
206
|
+
}
|
|
207
|
+
export { sell, share, stop };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AnnonError } from '../errors.js';
|
|
2
|
+
import { readShop } from '../shops/discovery.js';
|
|
3
|
+
/** Hosting comes only from a selected, locally approved deployment profile. */
|
|
4
|
+
export async function shareHost(config, id, fetcher) {
|
|
5
|
+
const selected = id ?? config.defaultShop;
|
|
6
|
+
const shop = config.shops?.find((s) => s.id === selected || s.manifest === selected);
|
|
7
|
+
if (!shop)
|
|
8
|
+
throw new AnnonError('invalid_config');
|
|
9
|
+
const endpoint = (a, b) => {
|
|
10
|
+
try {
|
|
11
|
+
return new URL(a).href === new URL(b).href;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const p = shop.profile;
|
|
18
|
+
if (!p)
|
|
19
|
+
throw new AnnonError('trust_required');
|
|
20
|
+
if (p.contract !== config.contractId ||
|
|
21
|
+
p.network !== config.network ||
|
|
22
|
+
!endpoint(p.nearRpc, config.nearRpc) ||
|
|
23
|
+
(p.baseRpc ? !endpoint(p.baseRpc, config.baseRpc) : config.baseRpc !== '') ||
|
|
24
|
+
p.relayer !== config.relayer)
|
|
25
|
+
throw new AnnonError('config_conflict');
|
|
26
|
+
const checked = await readShop(shop, fetcher);
|
|
27
|
+
return checked.info.host;
|
|
28
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type DiscoveryConfig } from '../shops/discovery.js';
|
|
2
|
+
import type { Input } from './cli.js';
|
|
3
|
+
import type { Environment } from './wallets.js';
|
|
4
|
+
export declare const isDiscovery: (input: Input) => boolean;
|
|
5
|
+
/** Discovery deliberately does not load deployment, signing or spending configuration. */
|
|
6
|
+
export declare function discoverShops(input: Input, env: Environment, agent: boolean, fetcher?: typeof fetch): Promise<Record<string, unknown>>;
|
|
7
|
+
/** Descriptive text retains its canonical shop provenance. */
|
|
8
|
+
export declare function selectedShopRules(config: DiscoveryConfig, id: string | undefined, fetcher: typeof fetch): Promise<{
|
|
9
|
+
shop?: never;
|
|
10
|
+
rules?: never;
|
|
11
|
+
} | {
|
|
12
|
+
shop: {
|
|
13
|
+
manifest: string;
|
|
14
|
+
name: string;
|
|
15
|
+
};
|
|
16
|
+
rules: import("../shops/discovery.js").ShopRules;
|
|
17
|
+
}>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
import { AnnonError } from '../errors.js';
|
|
5
|
+
import { canonicalManifestUrl, catalog, listShops, readShop, validateDiscoveryConfig, } from '../shops/discovery.js';
|
|
6
|
+
import { configPath } from './agent.js';
|
|
7
|
+
export const isDiscovery = (input) => ['shops', 'shop', 'catalog'].includes(input.command);
|
|
8
|
+
async function readConfig(path) {
|
|
9
|
+
try {
|
|
10
|
+
const value = JSON.parse(await readFile(path, 'utf8'));
|
|
11
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
12
|
+
throw new Error();
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
catch (cause) {
|
|
16
|
+
if (cause.code === 'ENOENT')
|
|
17
|
+
return {};
|
|
18
|
+
throw new AnnonError('invalid_config', undefined, { cause });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Discovery deliberately does not load deployment, signing or spending configuration. */
|
|
22
|
+
export async function discoverShops(input, env, agent, fetcher = globalThis.fetch) {
|
|
23
|
+
if (input.command === 'shop' && agent)
|
|
24
|
+
throw new AnnonError('human_only');
|
|
25
|
+
const path = configPath(input, env);
|
|
26
|
+
const raw = await readConfig(path);
|
|
27
|
+
const config = validateDiscoveryConfig(raw);
|
|
28
|
+
const query = {
|
|
29
|
+
...(input.search !== undefined ? { search: input.search } : {}),
|
|
30
|
+
...(input.limit !== undefined ? { limit: input.limit } : {}),
|
|
31
|
+
...(input.offset !== undefined ? { offset: input.offset } : {}),
|
|
32
|
+
};
|
|
33
|
+
if (input.command === 'shops')
|
|
34
|
+
return { ...(await listShops(config, query, fetcher)) };
|
|
35
|
+
if (input.command === 'catalog')
|
|
36
|
+
return {
|
|
37
|
+
...(await catalog(config, { ...query, ...(input.shop !== undefined ? { shop: input.shop } : {}) }, fetcher)),
|
|
38
|
+
};
|
|
39
|
+
if (input.command !== 'shop' || !input.target)
|
|
40
|
+
throw new AnnonError('invalid_arguments');
|
|
41
|
+
const manifest = canonicalManifestUrl(input.target);
|
|
42
|
+
const shops = config.shops ?? [];
|
|
43
|
+
const existing = shops.find((s) => s.manifest === manifest);
|
|
44
|
+
if ((input.id !== undefined && shops.some((s) => s.id === input.id && s.manifest !== manifest)) ||
|
|
45
|
+
(existing?.id !== undefined && input.id !== undefined && existing.id !== input.id))
|
|
46
|
+
throw new AnnonError('config_conflict');
|
|
47
|
+
const added = { ...existing, manifest, ...(input.id !== undefined ? { id: input.id } : {}) };
|
|
48
|
+
const next = existing ? shops.map((s) => (s === existing ? added : s)) : [...shops, added];
|
|
49
|
+
validateDiscoveryConfig({ ...config, shops: next });
|
|
50
|
+
await readShop(added, fetcher);
|
|
51
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
52
|
+
const tmp = `${path}.${randomUUID()}.tmp`;
|
|
53
|
+
await writeFile(tmp, JSON.stringify({ ...raw, shops: next }, null, 2), {
|
|
54
|
+
mode: 0o600,
|
|
55
|
+
flag: 'wx',
|
|
56
|
+
});
|
|
57
|
+
await rename(tmp, path);
|
|
58
|
+
return {
|
|
59
|
+
manifest,
|
|
60
|
+
...(added.id !== undefined ? { id: added.id } : {}),
|
|
61
|
+
trusted: added.profile !== undefined,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** Descriptive text retains its canonical shop provenance. */
|
|
65
|
+
export async function selectedShopRules(config, id, fetcher) {
|
|
66
|
+
const selected = id ?? config.defaultShop;
|
|
67
|
+
const local = config.shops?.find((s) => s.id === selected || s.manifest === selected);
|
|
68
|
+
if (!local)
|
|
69
|
+
return {};
|
|
70
|
+
const shop = await readShop(local, fetcher);
|
|
71
|
+
return shop.info.rules
|
|
72
|
+
? { shop: { manifest: shop.manifest, name: shop.info.name }, rules: shop.info.rules }
|
|
73
|
+
: {};
|
|
74
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Reference in-memory adapter for tests/provider integrations; inject signing and policy. */
|
|
2
|
+
import { type Operation, type SigningRequest, type SigningResult } from './signing.js';
|
|
3
|
+
export declare function signerMock(o: {
|
|
4
|
+
credential: string;
|
|
5
|
+
account: string;
|
|
6
|
+
chain: string;
|
|
7
|
+
operations: Operation[];
|
|
8
|
+
approve: (q: SigningRequest) => Promise<SigningResult | 'pending' | 'rejected'>;
|
|
9
|
+
}): ((request: Request) => Promise<Response>) & {
|
|
10
|
+
decide(id: string, result: SigningResult | "rejected"): void;
|
|
11
|
+
};
|