// Minimal NEAR wire format shared by the browser and the Worker.// Only what Duly needs: base58, borsh for transactions and NEP-366 delegate// actions, and a strict decoder the relayer uses on untrusted bytes.const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';const INDEX = Object.fromEntries([...ALPHABET].map((c, i) => [c, i]));export function b58encode(bytes) { let n = 0n; for (const b of bytes) n = (n << 8n) | BigInt(b); let out = ''; while (n > 0n) { out = ALPHABET[Number(n % 58n)] + out; n /= 58n; } for (const b of bytes) { if (b !== 0) break; out = '1' + out; } return out;}export function b58decode(s) { let n = 0n; for (const c of s) { if (!(c in INDEX)) throw new Error('Bad base58'); n = n * 58n + BigInt(INDEX[c]); } const out = []; while (n > 0n) { out.unshift(Number(n & 255n)); n >>= 8n; } for (const c of s) { if (c !== '1') break; out.unshift(0); } return Uint8Array.from(out);}export function hex(bytes) { return [...bytes].map((b) => b.toString(16).padStart(2, '0')).join('');}export function unhex(s) { if (!/^([0-9a-f]{2})*$/.test(s)) throw new Error('Bad hex'); return Uint8Array.from(s.match(/../g) || [], (x) => parseInt(x, 16));}export function b64encode(bytes) { let s = ''; for (let i = 0; i < bytes.length; i += 0x8000) { s += String.fromCharCode(...bytes.subarray(i, i + 0x8000)); } return btoa(s);}export function b64decode(s) { const bin = atob(s); const out = new Uint8Array(bin.length); for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i); return out;}export function concat(...parts) { const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0)); let o = 0; for (const p of parts) { out.set(p, o); o += p.length; } return out;}export async function sha256(bytes) { return new Uint8Array(await crypto.subtle.digest('SHA-256', bytes));}const te = new TextEncoder();const td = new TextDecoder('utf-8', { fatal: true });class Writer { constructor() { this.parts = []; } u8(v) { this.parts.push(Uint8Array.of(v)); return this; } u32(v) { const b = new Uint8Array(4); new DataView(b.buffer).setUint32(0, v, true); this.parts.push(b); return this; } u64(v) { const b = new Uint8Array(8); new DataView(b.buffer).setBigUint64(0, BigInt(v), true); this.parts.push(b); return this; } u128(v) { const n = BigInt(v); return this.u64(n & 0xffffffffffffffffn).u64(n >> 64n); } fixed(bytes) { this.parts.push(bytes); return this; } bytes(bytes) { return this.u32(bytes.length).fixed(bytes); } string(s) { return this.bytes(te.encode(s)); } done() { return concat(...this.parts); }}class Reader { constructor(bytes) { this.b = bytes; this.dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); this.o = 0; } need(n) { if (this.o + n > this.b.length) throw new Error('Truncated'); } u8() { this.need(1); return this.b[this.o++]; } u32() { this.need(4); const v = this.dv.getUint32(this.o, true); this.o += 4; return v; } u64() { this.need(8); const v = this.dv.getBigUint64(this.o, true); this.o += 8; return v; } u128() { const lo = this.u64(); const hi = this.u64(); return (hi << 64n) | lo; } fixed(n) { this.need(n); const v = this.b.slice(this.o, this.o + n); this.o += n; return v; } bytes(max) { const n = this.u32(); if (n > max) throw new Error('Too long'); return this.fixed(n); } string(max) { return td.decode(this.bytes(max)); } end() { if (this.o !== this.b.length) throw new Error('Trailing bytes'); }}const ED25519 = 0;function writePublicKey(w, pub) { return w.u8(ED25519).fixed(pub);}function writeSignature(w, sig) { return w.u8(ED25519).fixed(sig);}function writeAction(w, a) { switch (a.type) { case 'FunctionCall': return w .u8(2) .string(a.method) .bytes(a.args) .u64(a.gas) .u128(a.deposit); case 'Transfer': return w.u8(3).u128(a.deposit); case 'CreateAccount': return w.u8(0); case 'DeployContract': return w.u8(1).bytes(a.code); case 'AddFunctionCallKey': { writePublicKey(w.u8(5), a.publicKey).u64(0).u8(0).u8(1).u128(a.allowance).string(a.receiverId); w.u32(a.methods.length); for (const m of a.methods) w.string(m); return w; } case 'AddFullAccessKey': return writePublicKey(w.u8(5), a.publicKey).u64(0).u8(1); case 'DeleteKey': return writePublicKey(w.u8(6), a.publicKey); case 'Delegate': writeDelegate(w.u8(8), a.delegate); return writeSignature(w, a.signature); default: throw new Error('Unsupported action'); }}function writeDelegate(w, d) { w.string(d.senderId).string(d.receiverId).u32(d.actions.length); for (const a of d.actions) writeAction(w, a); w.u64(d.nonce).u64(d.maxBlockHeight); return writePublicKey(w, d.publicKey);}export function encodeDelegate(d) { return writeDelegate(new Writer(), d).done();}/** NEP-461 prefix (2^30 + 366) keeps a delegate signature from being a valid transaction signature. */const DELEGATE_PREFIX = new Writer().u32(1073742190).done();export async function delegateHash(d) { return sha256(concat(DELEGATE_PREFIX, encodeDelegate(d)));}export function encodeSignedDelegate(d, signature) { const w = new Writer(); writeDelegate(w, d); return writeSignature(w, signature).done();}export function encodeTransaction(t) { const w = new Writer(); w.string(t.signerId); writePublicKey(w, t.publicKey); w.u64(t.nonce).string(t.receiverId).fixed(t.blockHash).u32(t.actions.length); for (const a of t.actions) writeAction(w, a); return w.done();}export function encodeSignedTransaction(txBytes, signature) { const w = new Writer(); w.fixed(txBytes); return writeSignature(w, signature).done();}export const MAX_ARGS_BYTES = 2048;const MAX_NAME = 64;const MAX_METHOD = 64;/** * Strict decoder for a SignedDelegateAction from an untrusted client. It accepts * exactly one shape: one FunctionCall, ed25519 only, no trailing bytes. * Anything else throws, including nested delegates, transfers and key changes. */export function decodeSignedDelegate(bytes) { const r = new Reader(bytes); const senderId = r.string(MAX_NAME); const receiverId = r.string(MAX_NAME); const count = r.u32(); if (count !== 1) throw new Error('Exactly one action is allowed'); if (r.u8() !== 2) throw new Error('Only function calls are allowed'); const method = r.string(MAX_METHOD); const args = r.bytes(MAX_ARGS_BYTES); const gas = r.u64(); const deposit = r.u128(); const nonce = r.u64(); const maxBlockHeight = r.u64(); if (r.u8() !== ED25519) throw new Error('Only ed25519 keys are allowed'); const publicKey = r.fixed(32); if (r.u8() !== ED25519) throw new Error('Only ed25519 signatures are allowed'); const signature = r.fixed(64); r.end(); return { delegate: { senderId, receiverId, actions: [{ type: 'FunctionCall', method, args, gas, deposit }], nonce, maxBlockHeight, publicKey, }, signature, };}export function publicKeyString(pub) { return 'ed25519:' + b58encode(pub);}export function implicitAccount(pub) { return hex(pub);}export function toYocto(near) { const [whole, frac = ''] = String(near).split('.'); return BigInt(whole + frac.padEnd(24, '0').slice(0, 24));}export const TGAS = 1_000_000_000_000n;// ------------------------------------------------------------------ RPCexport class RpcError extends Error { constructor(message, detail) { super(message); this.detail = detail; }}export async function rpc(urls, method, params, { fetchImpl = fetch, timeoutMs = 20000 } = {}) { const list = Array.isArray(urls) ? urls : [urls]; let last; for (const url of list) { try { const res = await fetchImpl(url, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 'duly', method, params }), signal: AbortSignal.timeout(timeoutMs), }); if (!res.ok) throw new Error(`RPC HTTP ${res.status}`); const j = await res.json(); if (j.error) throw new RpcError(j.error.message || 'RPC error', j.error); return j.result; } catch (e) { last = e; if (e instanceof RpcError && e.detail?.name !== 'INTERNAL_ERROR') throw e; } } throw last;}export async function viewCall(urls, contract, method, args = {}, opts) { const r = await rpc( urls, 'query', { request_type: 'call_function', finality: 'optimistic', account_id: contract, method_name: method, args_base64: b64encode(te.encode(JSON.stringify(args))), }, opts, ); if (r.error) throw new RpcError(r.error); return JSON.parse(new TextDecoder().decode(Uint8Array.from(r.result)));}export async function accessKeyNonce(urls, account, publicKey, opts) { const r = await rpc( urls, 'query', { request_type: 'view_access_key', finality: 'optimistic', account_id: account, public_key: publicKey, }, opts, ); if (r.error) return null; return { nonce: BigInt(r.nonce), blockHash: r.block_hash, blockHeight: BigInt(r.block_height), permission: r.permission };}export async function accountExists(urls, account, opts) { try { const r = await rpc( urls, 'query', { request_type: 'view_account', finality: 'optimistic', account_id: account }, opts, ); return !r.error; } catch (e) { if (/does not exist|UNKNOWN_ACCOUNT/i.test(`${e.message} ${JSON.stringify(e.detail || {})}`)) return false; throw e; }}/** First failure inside a final outcome, or null when every receipt succeeded. */export function outcomeFailure(outcome) { const all = [outcome.transaction_outcome, ...(outcome.receipts_outcome || [])]; for (const o of all) { const s = o?.outcome?.status; if (s && s.Failure) return s.Failure; } return null;}/** Plain-language text for a contract panic inside a failure object. */export function failureText(f) { const text = JSON.stringify(f); const m = text.match(/Smart contract panicked: ([^"\\]+)/); return m ? m[1] : text.slice(0, 200);}/** Duly events logged by any receipt of this transaction, in order. */export function outcomeEvents(outcome, standard = 'duly') { const out = []; for (const r of outcome?.receipts_outcome || []) { for (const line of r.outcome?.logs || []) { if (!line.startsWith('EVENT_JSON:')) continue; try { const e = JSON.parse(line.slice('EVENT_JSON:'.length)); if (e.standard === standard) out.push({ event: e.event, data: e.data?.[0] }); } catch { // a malformed log is ignored } } } return out;}/** JSON value the last receipt returned, if any. */export function outcomeValue(outcome) { const v = outcome?.status?.SuccessValue; if (!v) return null; try { return JSON.parse(new TextDecoder().decode(b64decode(v))); } catch { return null; }}