@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,79 @@
|
|
|
1
|
+
/** Reference in-memory adapter for tests/provider integrations; inject signing and policy. */
|
|
2
|
+
import { json } from './signing.js';
|
|
3
|
+
const canonical = (x) => x && typeof x === 'object'
|
|
4
|
+
? Array.isArray(x)
|
|
5
|
+
? `[${x.map(canonical)}]`
|
|
6
|
+
: `{${Object.keys(x)
|
|
7
|
+
.sort()
|
|
8
|
+
.map((k) => `${JSON.stringify(k)}:${canonical(x[k])}`)
|
|
9
|
+
.join(',')}}`
|
|
10
|
+
: JSON.stringify(x);
|
|
11
|
+
export function signerMock(o) {
|
|
12
|
+
const requests = new Map();
|
|
13
|
+
const handle = async (request) => {
|
|
14
|
+
if (request.headers.get('authorization') !== `Bearer ${o.credential}`)
|
|
15
|
+
return new Response(null, { status: 401 });
|
|
16
|
+
const path = new URL(request.url).pathname;
|
|
17
|
+
if (path === '/v1/capabilities' && request.method === 'GET')
|
|
18
|
+
return Response.json({
|
|
19
|
+
version: 1,
|
|
20
|
+
account: o.account,
|
|
21
|
+
chain: o.chain,
|
|
22
|
+
operations: o.operations,
|
|
23
|
+
});
|
|
24
|
+
let id;
|
|
25
|
+
if (path === '/v1/requests' && request.method === 'POST') {
|
|
26
|
+
const q = (await request.json());
|
|
27
|
+
if (q.version !== 1 ||
|
|
28
|
+
!/^[a-zA-Z0-9-]{1,100}$/.test(q.id) ||
|
|
29
|
+
q.account !== o.account ||
|
|
30
|
+
q.chain !== o.chain ||
|
|
31
|
+
!o.operations.includes(q.operation) ||
|
|
32
|
+
!Number.isSafeInteger(q.expiresAt) ||
|
|
33
|
+
q.expiresAt <= Date.now())
|
|
34
|
+
return new Response(null, { status: 400 });
|
|
35
|
+
id = q.id;
|
|
36
|
+
const exact = canonical(q);
|
|
37
|
+
const old = requests.get(id);
|
|
38
|
+
if (old && old.exact !== exact)
|
|
39
|
+
return new Response(null, { status: 409 });
|
|
40
|
+
if (!old) {
|
|
41
|
+
// Claim before awaiting provider approval, so duplicate requests cannot sign twice.
|
|
42
|
+
requests.set(id, { exact, q, status: 'pending' });
|
|
43
|
+
const result = await o.approve(JSON.parse(json(q)));
|
|
44
|
+
const entry = requests.get(id);
|
|
45
|
+
if (!entry)
|
|
46
|
+
throw new Error('missing claimed request');
|
|
47
|
+
if (result === 'pending' || result === 'rejected')
|
|
48
|
+
entry.status = result;
|
|
49
|
+
else {
|
|
50
|
+
entry.status = 'approved';
|
|
51
|
+
entry.result = result;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else if (request.method === 'GET' && path.startsWith('/v1/requests/'))
|
|
56
|
+
id = path.slice('/v1/requests/'.length);
|
|
57
|
+
else
|
|
58
|
+
return new Response(null, { status: 404 });
|
|
59
|
+
const entry = requests.get(id);
|
|
60
|
+
if (!entry)
|
|
61
|
+
return new Response(null, { status: 404 });
|
|
62
|
+
if (entry.q.expiresAt <= Date.now())
|
|
63
|
+
return new Response(null, { status: 410 });
|
|
64
|
+
return Response.json({ version: 1, id, status: entry.status, ...(entry.result ? { result: entry.result } : {}) }, { headers: { 'cache-control': 'no-store' } });
|
|
65
|
+
};
|
|
66
|
+
return Object.assign(handle, {
|
|
67
|
+
decide(id, result) {
|
|
68
|
+
const entry = requests.get(id);
|
|
69
|
+
if (entry?.status !== 'pending' || entry.q.expiresAt <= Date.now())
|
|
70
|
+
throw new Error('no pending approval');
|
|
71
|
+
if (result === 'rejected')
|
|
72
|
+
entry.status = 'rejected';
|
|
73
|
+
else {
|
|
74
|
+
entry.status = 'approved';
|
|
75
|
+
entry.result = result;
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type SignerConfig } from './agent.js';
|
|
2
|
+
import type { BrowserSession } from './browser-login.js';
|
|
3
|
+
import type { Input } from './cli.js';
|
|
4
|
+
import type { Config } from './config.js';
|
|
5
|
+
import { type Keychain } from './keychain.js';
|
|
6
|
+
import { type Environment, type LocalWallet } from './wallets.js';
|
|
7
|
+
export declare function assertSignerInput(o: Partial<Input>): void;
|
|
8
|
+
export declare function configuredWallet(s: SignerConfig, c: Config, o: Input, env: Environment, keychain: Keychain, browser: () => Promise<BrowserSession>, terms: () => unknown): Promise<LocalWallet>;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { sha256 } from '@noble/hashes/sha2.js';
|
|
3
|
+
import { base58, base64 } from '@scure/base';
|
|
4
|
+
import { Account, actions, PublicKey } from 'near-api-js';
|
|
5
|
+
import { signNearUpload } from '../browser/wallets.js';
|
|
6
|
+
import { proveNearControl } from '../near/nep413.js';
|
|
7
|
+
import { rpcReply, txOutcome } from '../near/rpc.js';
|
|
8
|
+
import { parseIdentity } from '../settle-near/identity.js';
|
|
9
|
+
import { parsePurchaseMessage, purchaseId } from '../settle-near/purchase.js';
|
|
10
|
+
import { TransactionUncertain } from '../wallet/uncertain.js';
|
|
11
|
+
import { nearSelectorWallet } from '../wallet/wallet.js';
|
|
12
|
+
import { validateSigner } from './agent.js';
|
|
13
|
+
import { AdapterError } from './errors.js';
|
|
14
|
+
import { decodeKeychain } from './keychain.js';
|
|
15
|
+
import { remoteSigner, signingRequest, verifySigningResult, } from './signing.js';
|
|
16
|
+
import { loadWallet } from './wallets.js';
|
|
17
|
+
export function assertSignerInput(o) {
|
|
18
|
+
if (o.evmKeyFile !== undefined || o.nearKeyFile !== undefined || o.solanaKeyFile !== undefined)
|
|
19
|
+
throw new AdapterError('signer_init_only', 'Key-file overrides are refused with a configured signer.', 2);
|
|
20
|
+
}
|
|
21
|
+
export async function configuredWallet(s, c, o, env, keychain, browser, terms) {
|
|
22
|
+
assertSignerInput(o);
|
|
23
|
+
validateSigner(s);
|
|
24
|
+
if (s.mode === 'file' || s.mode === 'env' || s.mode === 'keychain') {
|
|
25
|
+
const key = s.mode === 'file'
|
|
26
|
+
? (await readFile(s.file, 'utf8')).trim()
|
|
27
|
+
: s.mode === 'env'
|
|
28
|
+
? env[s.name]
|
|
29
|
+
: decodeKeychain(await keychain.read(s.entry));
|
|
30
|
+
const keyEnv = s.kind === 'evm'
|
|
31
|
+
? 'ANNON_EVM_PRIVATE_KEY'
|
|
32
|
+
: s.kind === 'near'
|
|
33
|
+
? 'ANNON_NEAR_CREDENTIALS'
|
|
34
|
+
: 'ANNON_SOLANA_KEYPAIR';
|
|
35
|
+
return loadWallet({
|
|
36
|
+
...o,
|
|
37
|
+
wallet: s.kind,
|
|
38
|
+
...(s.identity ? { recovery: `${s.identity.kind}:${s.identity.id}` } : {}),
|
|
39
|
+
}, { [keyEnv]: key }, c.nearRpc, keychain);
|
|
40
|
+
}
|
|
41
|
+
const id = parseIdentity(s.identity);
|
|
42
|
+
const session = s.mode === 'browser' ? await browser() : undefined;
|
|
43
|
+
const transport = s.mode === 'remote'
|
|
44
|
+
? remoteSigner({
|
|
45
|
+
url: s.url,
|
|
46
|
+
credential: async () => 'env' in s.credential
|
|
47
|
+
? (env[s.credential.env] ?? '')
|
|
48
|
+
: await keychain.read(s.credential.keychain),
|
|
49
|
+
})
|
|
50
|
+
: undefined;
|
|
51
|
+
const sign = async (operation, payload) => {
|
|
52
|
+
const q = signingRequest(operation, id.kind === 'evm' ? 'eip155:8453' : c.network, id.id, payload);
|
|
53
|
+
q.terms = terms();
|
|
54
|
+
const verify = (r) => verifySigningResult(q, r, c.nearRpc);
|
|
55
|
+
if (transport)
|
|
56
|
+
return transport(q, verify);
|
|
57
|
+
if (!session)
|
|
58
|
+
throw new Error('missing browser session');
|
|
59
|
+
return session.request(q, verify);
|
|
60
|
+
};
|
|
61
|
+
if (id.kind === 'evm')
|
|
62
|
+
return {
|
|
63
|
+
identity: id,
|
|
64
|
+
address: id.id,
|
|
65
|
+
authorize: async (message) => ({
|
|
66
|
+
kind: 'evm',
|
|
67
|
+
sig: (await sign('evm.personalSign', { message })).signature,
|
|
68
|
+
}),
|
|
69
|
+
signTypedData: async (data) => (await sign('evm.signTypedData', data)).signature,
|
|
70
|
+
};
|
|
71
|
+
if (id.kind !== 'near')
|
|
72
|
+
throw new Error('browser/remote Solana signing is not supported');
|
|
73
|
+
const signMessage = async (p) => (await sign('near.nep413', p));
|
|
74
|
+
const wallet = nearSelectorWallet({
|
|
75
|
+
getAccounts: async () => [{ accountId: id.id }],
|
|
76
|
+
signAndSendTransaction: async (tx) => {
|
|
77
|
+
const fc = tx.actions[0]?.params;
|
|
78
|
+
if (!fc)
|
|
79
|
+
throw new Error('missing action');
|
|
80
|
+
let payload = {
|
|
81
|
+
receiverId: tx.receiverId,
|
|
82
|
+
method: fc.methodName,
|
|
83
|
+
args: fc.args,
|
|
84
|
+
gas: fc.gas,
|
|
85
|
+
deposit: fc.deposit,
|
|
86
|
+
};
|
|
87
|
+
if (s.mode === 'remote') {
|
|
88
|
+
if (!s.publicKey)
|
|
89
|
+
throw new Error('remote NEAR transactions require the public key in config');
|
|
90
|
+
// Transport exception near_api_remote_signer: Account builds the unsigned transaction.
|
|
91
|
+
// Its provider uses the third-party Node fetch transport for nonce and block lookup.
|
|
92
|
+
const unsigned = await new Account(id.id, c.nearRpc).createTransaction({
|
|
93
|
+
receiverId: tx.receiverId,
|
|
94
|
+
publicKey: PublicKey.fromString(s.publicKey),
|
|
95
|
+
actions: [
|
|
96
|
+
actions.functionCall(fc.methodName, fc.args, BigInt(fc.gas), BigInt(fc.deposit)),
|
|
97
|
+
],
|
|
98
|
+
});
|
|
99
|
+
payload = { ...payload, transaction: base64.encode(unsigned.encode()) };
|
|
100
|
+
}
|
|
101
|
+
const msg = fc.args.msg;
|
|
102
|
+
const purchase = fc.methodName === 'ft_transfer_call' && msg
|
|
103
|
+
? purchaseId(parsePurchaseMessage(msg))
|
|
104
|
+
: undefined;
|
|
105
|
+
let knownHash;
|
|
106
|
+
try {
|
|
107
|
+
const result = await sign('near.signTransaction', payload);
|
|
108
|
+
let hash = result.txHash;
|
|
109
|
+
if (result.signedTransaction) {
|
|
110
|
+
const { decodeSignedTransaction } = await import('near-api-js');
|
|
111
|
+
hash = base58.encode(sha256(decodeSignedTransaction(base64.decode(result.signedTransaction)).transaction.encode()));
|
|
112
|
+
knownHash = hash;
|
|
113
|
+
const sent = await rpcReply(c.nearRpc, 'send_tx', {
|
|
114
|
+
signed_tx_base64: result.signedTransaction,
|
|
115
|
+
wait_until: 'FINAL',
|
|
116
|
+
});
|
|
117
|
+
if (sent.error)
|
|
118
|
+
throw new TransactionUncertain(hash, purchase);
|
|
119
|
+
await verifySigningResult(signingRequest('near.signTransaction', c.network, id.id, payload), { txHash: hash }, c.nearRpc);
|
|
120
|
+
}
|
|
121
|
+
if (!hash)
|
|
122
|
+
throw new Error('missing transaction hash');
|
|
123
|
+
knownHash = hash;
|
|
124
|
+
const out = await txOutcome(c.nearRpc, hash, id.id, tx.receiverId, 'FINAL', {
|
|
125
|
+
method: fc.methodName,
|
|
126
|
+
args: fc.args,
|
|
127
|
+
});
|
|
128
|
+
if (out.state === 'released')
|
|
129
|
+
return out.answer;
|
|
130
|
+
if (out.state === 'done')
|
|
131
|
+
return out.value;
|
|
132
|
+
throw new TransactionUncertain(hash, purchase);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
if (error instanceof TransactionUncertain)
|
|
136
|
+
throw new TransactionUncertain(error.txHash, purchase);
|
|
137
|
+
if (knownHash)
|
|
138
|
+
throw new TransactionUncertain(knownHash, purchase);
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
}, id.id,
|
|
143
|
+
// Browser NEAR wallets return NEP-413 signatures in the page, so the payload has no callbackUrl.
|
|
144
|
+
(text, context) => proveNearControl(id.id, text, context, signMessage));
|
|
145
|
+
return {
|
|
146
|
+
...wallet,
|
|
147
|
+
signUpload: (intent) => signNearUpload({ getAccounts: async () => [{ accountId: id.id }], signMessage }, intent, c.nearRpc),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type Operation = 'near.nep413' | 'near.signTransaction' | 'evm.personalSign' | 'evm.signTypedData';
|
|
2
|
+
export interface SigningRequest {
|
|
3
|
+
version: 1;
|
|
4
|
+
id: string;
|
|
5
|
+
operation: Operation;
|
|
6
|
+
chain: string;
|
|
7
|
+
account: string;
|
|
8
|
+
expiresAt: number;
|
|
9
|
+
payload: unknown;
|
|
10
|
+
terms?: unknown;
|
|
11
|
+
}
|
|
12
|
+
export type SigningResult = {
|
|
13
|
+
signature?: string;
|
|
14
|
+
account?: string;
|
|
15
|
+
publicKey?: string;
|
|
16
|
+
accountId?: string;
|
|
17
|
+
signedTransaction?: string;
|
|
18
|
+
txHash?: string;
|
|
19
|
+
};
|
|
20
|
+
export declare const json: (v: unknown) => string;
|
|
21
|
+
export declare function signingRequest(operation: Operation, chain: string, account: string, payload: unknown, ttl?: number): SigningRequest;
|
|
22
|
+
/** Both transports use this verifier; no response is evidence merely because it was approved. */
|
|
23
|
+
export declare function verifySigningResult(q: SigningRequest, r: SigningResult, nearRpc: string): Promise<void>;
|
|
24
|
+
export declare function remoteSigner(o: {
|
|
25
|
+
url: string;
|
|
26
|
+
credential: () => Promise<string>;
|
|
27
|
+
fetch?: typeof fetch;
|
|
28
|
+
wait?: (ms: number) => Promise<void>;
|
|
29
|
+
}): (q: SigningRequest, verify: (r: SigningResult) => Promise<void>) => Promise<SigningResult>;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { ed25519 } from '@noble/curves/ed25519.js';
|
|
3
|
+
import { sha256 } from '@noble/hashes/sha2.js';
|
|
4
|
+
import { base58, base64 } from '@scure/base';
|
|
5
|
+
import { decodeSignedTransaction } from 'near-api-js';
|
|
6
|
+
import { verifyTypedData } from 'viem';
|
|
7
|
+
import { isTransient } from '../errors.js';
|
|
8
|
+
import { verifyNearMessage } from '../near/nep413.js';
|
|
9
|
+
import { isFullAccessKey, readOutcome, rpcReply } from '../near/rpc.js';
|
|
10
|
+
import { sleep } from '../net/retry.js';
|
|
11
|
+
import { parseIdentity, verifyAuthorization } from '../settle-near/identity.js';
|
|
12
|
+
import { TransactionUncertain } from '../wallet/uncertain.js';
|
|
13
|
+
export const json = (v) => JSON.stringify(v, (_, x) => typeof x === 'bigint' ? String(x) : x instanceof Uint8Array ? [...x] : x);
|
|
14
|
+
export function signingRequest(operation, chain, account, payload, ttl = 120000) {
|
|
15
|
+
return {
|
|
16
|
+
version: 1,
|
|
17
|
+
id: randomUUID(),
|
|
18
|
+
operation,
|
|
19
|
+
chain,
|
|
20
|
+
account,
|
|
21
|
+
expiresAt: Date.now() + ttl,
|
|
22
|
+
payload: JSON.parse(json(payload)),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Both transports use this verifier; no response is evidence merely because it was approved. */
|
|
26
|
+
export async function verifySigningResult(q, r, nearRpc) {
|
|
27
|
+
if (Date.now() >= q.expiresAt)
|
|
28
|
+
throw new Error('signing request expired');
|
|
29
|
+
if (q.operation.startsWith('evm.')) {
|
|
30
|
+
const identity = parseIdentity({ kind: 'evm', id: q.account });
|
|
31
|
+
if (!/^eip155:[1-9][0-9]*$/.test(q.chain) || typeof r.signature !== 'string')
|
|
32
|
+
throw new Error('invalid EVM response');
|
|
33
|
+
if (q.operation === 'evm.personalSign') {
|
|
34
|
+
const p = q.payload;
|
|
35
|
+
if (typeof p.message !== 'string' ||
|
|
36
|
+
!verifyAuthorization(identity, p.message, { kind: 'evm', sig: r.signature }))
|
|
37
|
+
throw new Error('signature mismatch');
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const p = q.payload;
|
|
41
|
+
if (`eip155:${p.domain?.chainId}` !== q.chain ||
|
|
42
|
+
!(await verifyTypedData({ ...p, address: identity.id, signature: r.signature })))
|
|
43
|
+
throw new Error('typed data signature or chain mismatch');
|
|
44
|
+
}
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (!['near:mainnet', 'near:testnet', 'near:sandbox'].includes(q.chain))
|
|
48
|
+
throw new Error('invalid NEAR chain');
|
|
49
|
+
if (q.operation === 'near.nep413') {
|
|
50
|
+
const p = q.payload;
|
|
51
|
+
await verifyNearMessage(q.account, { ...p, nonce: Uint8Array.from(p.nonce) }, r, nearRpc);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const p = q.payload;
|
|
55
|
+
if (r.signedTransaction) {
|
|
56
|
+
if (!p.transaction)
|
|
57
|
+
throw new Error('missing exact unsigned transaction');
|
|
58
|
+
const signed = decodeSignedTransaction(base64.decode(r.signedTransaction));
|
|
59
|
+
const tx = signed.transaction;
|
|
60
|
+
const publicKey = tx.publicKey.toString();
|
|
61
|
+
const fc = tx.actions[0]?.functionCall;
|
|
62
|
+
const sameArgs = (a, b) => {
|
|
63
|
+
const stable = (v) => v && typeof v === 'object'
|
|
64
|
+
? Array.isArray(v)
|
|
65
|
+
? `[${v.map(stable)}]`
|
|
66
|
+
: `{${Object.keys(v)
|
|
67
|
+
.sort()
|
|
68
|
+
.map((k) => `${JSON.stringify(k)}:${stable(v[k])}`)
|
|
69
|
+
.join(',')}}`
|
|
70
|
+
: JSON.stringify(v);
|
|
71
|
+
return stable(a) === stable(b);
|
|
72
|
+
};
|
|
73
|
+
if (tx.receiverId !== p.receiverId ||
|
|
74
|
+
tx.actions.length !== 1 ||
|
|
75
|
+
!fc ||
|
|
76
|
+
fc.methodName !== p.method ||
|
|
77
|
+
String(fc.gas) !== p.gas ||
|
|
78
|
+
String(fc.deposit) !== p.deposit ||
|
|
79
|
+
!sameArgs(JSON.parse(new TextDecoder().decode(Uint8Array.from(fc.args))), p.args))
|
|
80
|
+
throw new Error('unsigned transaction action mismatch');
|
|
81
|
+
if (base64.encode(tx.encode()) !== p.transaction ||
|
|
82
|
+
tx.signerId !== q.account ||
|
|
83
|
+
!publicKey.startsWith('ed25519:') ||
|
|
84
|
+
!ed25519.verify(Uint8Array.from(signed.signature.ed25519Signature?.data ?? []), sha256(tx.encode()), base58.decode(publicKey.slice(8)), { zip215: false }) ||
|
|
85
|
+
!(await isFullAccessKey(nearRpc, q.account, publicKey)))
|
|
86
|
+
throw new Error('signed transaction mismatch');
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (!r.txHash)
|
|
90
|
+
throw new Error('missing transaction evidence');
|
|
91
|
+
let reply;
|
|
92
|
+
try {
|
|
93
|
+
// Verify one independently fetched result, avoiding a second inconsistent RPC snapshot.
|
|
94
|
+
reply = await rpcReply(nearRpc, 'tx', {
|
|
95
|
+
tx_hash: r.txHash,
|
|
96
|
+
sender_account_id: q.account,
|
|
97
|
+
wait_until: 'FINAL',
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (isTransient(error))
|
|
102
|
+
throw new TransactionUncertain(r.txHash);
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
const t = reply.result;
|
|
106
|
+
if (reply.error) {
|
|
107
|
+
if (['UNKNOWN_TRANSACTION', 'TIMEOUT_ERROR', 'INTERNAL_ERROR'].includes(reply.error.cause?.name ?? reply.error.name ?? ''))
|
|
108
|
+
throw new TransactionUncertain(r.txHash);
|
|
109
|
+
throw new Error('transaction RPC rejected the evidence');
|
|
110
|
+
}
|
|
111
|
+
if (t?.final_execution_status !== 'FINAL')
|
|
112
|
+
throw new TransactionUncertain(r.txHash);
|
|
113
|
+
const fc = t.transaction?.actions?.[0]?.FunctionCall;
|
|
114
|
+
if (String(fc?.gas) !== p.gas || fc?.deposit !== p.deposit)
|
|
115
|
+
throw new Error('transaction gas/deposit mismatch');
|
|
116
|
+
// Semantic rejections must never become nearSubmission evidence. readOutcome also checks
|
|
117
|
+
// hash, signer, receiver, method, args and execution status on this same finalized response.
|
|
118
|
+
const result = readOutcome(t, p.receiverId, {
|
|
119
|
+
txHash: r.txHash,
|
|
120
|
+
signer: q.account,
|
|
121
|
+
method: p.method,
|
|
122
|
+
args: p.args,
|
|
123
|
+
});
|
|
124
|
+
if (result.state === 'pending')
|
|
125
|
+
throw new TransactionUncertain(r.txHash);
|
|
126
|
+
if (!['done', 'released'].includes(result.state))
|
|
127
|
+
throw new Error('transaction did not succeed');
|
|
128
|
+
}
|
|
129
|
+
export function remoteSigner(o) {
|
|
130
|
+
const u = new URL(o.url);
|
|
131
|
+
if (u.protocol !== 'https:' ||
|
|
132
|
+
u.username ||
|
|
133
|
+
u.password ||
|
|
134
|
+
u.search ||
|
|
135
|
+
u.hash ||
|
|
136
|
+
(u.pathname !== '/' && u.pathname !== ''))
|
|
137
|
+
throw new TypeError('signer must be an HTTPS origin without credentials');
|
|
138
|
+
const fetcher = o.fetch ?? fetch;
|
|
139
|
+
return async (q, verify) => {
|
|
140
|
+
const credential = await o.credential();
|
|
141
|
+
if (!credential || /[\r\n]/.test(credential))
|
|
142
|
+
throw new Error('signer credential required');
|
|
143
|
+
const call = async (path, body) => {
|
|
144
|
+
if (Date.now() >= q.expiresAt)
|
|
145
|
+
throw new Error('signing request expired');
|
|
146
|
+
const res = await fetcher(new URL(path, u).href, {
|
|
147
|
+
method: body === undefined ? 'GET' : 'POST',
|
|
148
|
+
headers: { authorization: `Bearer ${credential}`, 'content-type': 'application/json' },
|
|
149
|
+
redirect: 'error',
|
|
150
|
+
credentials: 'omit',
|
|
151
|
+
cache: 'no-store',
|
|
152
|
+
signal: AbortSignal.timeout(Math.max(1, Math.min(20000, q.expiresAt - Date.now()))),
|
|
153
|
+
...(body === undefined ? {} : { body: json(body) }),
|
|
154
|
+
});
|
|
155
|
+
if (!res.ok)
|
|
156
|
+
throw new Error('signer unavailable');
|
|
157
|
+
return res.json();
|
|
158
|
+
};
|
|
159
|
+
const c = await call('/v1/capabilities');
|
|
160
|
+
if (c.version !== 1 ||
|
|
161
|
+
c.account !== q.account ||
|
|
162
|
+
c.chain !== q.chain ||
|
|
163
|
+
!Array.isArray(c.operations) ||
|
|
164
|
+
!c.operations.includes(q.operation))
|
|
165
|
+
throw new Error('signer capability mismatch');
|
|
166
|
+
let r;
|
|
167
|
+
// Only transport failures retry creation. Every attempt uses the same immutable request/id.
|
|
168
|
+
const exact = JSON.parse(json(q));
|
|
169
|
+
for (let attempt = 0;; attempt++) {
|
|
170
|
+
try {
|
|
171
|
+
r = await call('/v1/requests', exact);
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
if (!(error instanceof TypeError ||
|
|
176
|
+
(error instanceof Error && ['TimeoutError', 'AbortError'].includes(error.name))) ||
|
|
177
|
+
attempt >= 2 ||
|
|
178
|
+
Date.now() >= q.expiresAt)
|
|
179
|
+
throw error;
|
|
180
|
+
await (o.wait ?? sleep)(1000);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
for (;;) {
|
|
184
|
+
if (r.version !== 1 || r.id !== q.id)
|
|
185
|
+
throw new Error('signer response mismatch');
|
|
186
|
+
if (r.status === 'rejected')
|
|
187
|
+
throw new Error('signer rejected request');
|
|
188
|
+
if (r.status === 'approved') {
|
|
189
|
+
if (!r.result)
|
|
190
|
+
throw new Error('missing signer result');
|
|
191
|
+
await verify(r.result);
|
|
192
|
+
return r.result;
|
|
193
|
+
}
|
|
194
|
+
if (r.status !== 'pending')
|
|
195
|
+
throw new Error('invalid signer status');
|
|
196
|
+
await (o.wait ?? sleep)(1000);
|
|
197
|
+
r = await call(`/v1/requests/${encodeURIComponent(q.id)}`);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CommandSession, RequestContext } from './command-context.js';
|
|
2
|
+
/** `budget`: the file's available purchase storage and what one more purchase needs. */
|
|
3
|
+
declare function fileBudget(ctx: RequestContext): Promise<Record<string, unknown>>;
|
|
4
|
+
/**
|
|
5
|
+
* `fund`, first step: a NEAR owner funds the file directly. Returns nothing for other wallets,
|
|
6
|
+
* which fund through the SDK once it is ready; the loaded wallet stays selected for that step.
|
|
7
|
+
*/
|
|
8
|
+
declare function fundWithNearWallet(ctx: RequestContext): Promise<Record<string, unknown> | undefined>;
|
|
9
|
+
/** `fund` through the SDK, for owners that are not NEAR wallets. */
|
|
10
|
+
declare function fund(ctx: CommandSession): Promise<Record<string, unknown>>;
|
|
11
|
+
export { fileBudget, fund, fundWithNearWallet };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** File purchase storage: `budget` reads what a file has left, `fund` adds to it. */
|
|
2
|
+
import { fileStorageBalance } from '../near/rpc.js';
|
|
3
|
+
import { fundFileStorage } from '../wallet/wallet.js';
|
|
4
|
+
import { fail, isId } from './adapter-checks.js';
|
|
5
|
+
import { nearAmount } from './amounts.js';
|
|
6
|
+
import { usage } from './errors.js';
|
|
7
|
+
import { loadWallet } from './wallets.js';
|
|
8
|
+
/** `budget`: the file's available purchase storage and what one more purchase needs. */
|
|
9
|
+
async function fileBudget(ctx) {
|
|
10
|
+
const { c, o } = ctx;
|
|
11
|
+
if (!o.target || !isId(o.target))
|
|
12
|
+
throw usage();
|
|
13
|
+
const budget = await fileStorageBalance(c.nearRpc, c.contractId, o.target);
|
|
14
|
+
return {
|
|
15
|
+
fileId: o.target,
|
|
16
|
+
available: budget.available.toString(),
|
|
17
|
+
admissionRequirement: budget.admissionRequirement.toString(),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* `fund`, first step: a NEAR owner funds the file directly. Returns nothing for other wallets,
|
|
22
|
+
* which fund through the SDK once it is ready; the loaded wallet stays selected for that step.
|
|
23
|
+
*/
|
|
24
|
+
async function fundWithNearWallet(ctx) {
|
|
25
|
+
const { c, d, env, o } = ctx;
|
|
26
|
+
if (d.agent)
|
|
27
|
+
return fail('human_only', 'File funding requires a human CLI session.', 2);
|
|
28
|
+
if (!o.target || !isId(o.target))
|
|
29
|
+
throw usage();
|
|
30
|
+
const text = o.fundAmount;
|
|
31
|
+
if (!text)
|
|
32
|
+
return fail('invalid_arguments', 'Provide the NEAR amount to allocate.', 2);
|
|
33
|
+
const owner = await (d.wallet ?? loadWallet)({ ...o, network: c.network }, env, c.nearRpc);
|
|
34
|
+
ctx.selected = owner;
|
|
35
|
+
if (owner.identity.kind === 'near') {
|
|
36
|
+
if (!('call' in owner))
|
|
37
|
+
return fail('near_wallet_required', 'File funding requires a NEAR wallet.', 2);
|
|
38
|
+
await fundFileStorage(owner, c.contractId, o.target, nearAmount(text));
|
|
39
|
+
return { fileId: o.target, fundedNear: text };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** `fund` through the SDK, for owners that are not NEAR wallets. */
|
|
43
|
+
async function fund(ctx) {
|
|
44
|
+
const { o, sdk, wallet } = ctx;
|
|
45
|
+
if (!o.target || !o.fundAmount)
|
|
46
|
+
throw usage();
|
|
47
|
+
await sdk.fund(o.target, nearAmount(o.fundAmount), await wallet());
|
|
48
|
+
return { fileId: o.target, fundedNear: o.fundAmount };
|
|
49
|
+
}
|
|
50
|
+
export { fileBudget, fund, fundWithNearWallet };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function syncDir(dir: string): Promise<void>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { UploadAuth, UploadIntent } from '../download/upload.js';
|
|
2
|
+
import type { SplitCall } from '../pay/split.js';
|
|
3
|
+
import { type IdentityWallet } from '../wallet/wallet.js';
|
|
4
|
+
import type { Input } from './cli.js';
|
|
5
|
+
import { type Keychain } from './keychain.js';
|
|
6
|
+
export type Environment = Record<string, string | undefined>;
|
|
7
|
+
export type LocalWallet = IdentityWallet & {
|
|
8
|
+
signUpload?: (intent: UploadIntent) => Promise<UploadAuth>;
|
|
9
|
+
sendBase?: (call: SplitCall, rpc: string) => Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
export declare function identity(value: string): import("../settle-near/identity.js").Identity;
|
|
12
|
+
export declare function readBackupSecret(o: Partial<Input>, env: Environment): Promise<Uint8Array>;
|
|
13
|
+
export declare function loadWallet(o: Partial<Input>, env: Environment, nearRpc: string, keychain?: Keychain): Promise<LocalWallet>;
|