import test from 'node:test'; import assert from 'node:assert/strict'; import * as ed from '../web/vendor/ed25519.js'; import { b58decode, b58encode, b64encode, concat, decodeSignedDelegate, delegateHash, encodeDelegate, encodeSignedDelegate, encodeTransaction, hex, outcomeEvents, outcomeFailure, toYocto, TGAS, } from '../web/lib/near.js'; const seed = new Uint8Array(32).fill(7); const pub = await ed.getPublicKeyAsync(seed); const fc = { type: 'FunctionCall', method: 'register', args: new TextEncoder().encode('{}'), gas: 30n * TGAS, deposit: toYocto('0.01') }; const delegate = { senderId: hex(pub), receiverId: 'dulypay.testnet', actions: [fc], nonce: 5n, maxBlockHeight: 1000n, publicKey: pub }; test('base58 round trip, including leading zeros', () => { for (const b of [[0, 0, 1], [255, 255], [0], [1, 2, 3, 4, 5]]) { assert.deepEqual([...b58decode(b58encode(Uint8Array.from(b)))], b); } assert.throws(() => b58decode('0OIl')); }); test('known account key encodes as NEAR does', () => { // 32 bytes of 1 in base58, from the NEAR docs convention assert.equal(b58encode(new Uint8Array(32).fill(1)), '4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi'); }); test('signed delegate survives encode then strict decode', async () => { const sig = await ed.signAsync(await delegateHash(delegate), seed); const back = decodeSignedDelegate(encodeSignedDelegate(delegate, sig)); assert.equal(back.delegate.senderId, delegate.senderId); assert.equal(back.delegate.actions[0].method, 'register'); assert.equal(back.delegate.actions[0].deposit, fc.deposit); assert.equal(back.delegate.nonce, 5n); assert.deepEqual([...back.signature], [...sig]); assert.ok(await ed.verifyAsync(sig, await delegateHash(back.delegate), pub)); }); test('delegate hash uses the NEP-461 prefix, so it can never equal a transaction hash', async () => { const body = encodeDelegate(delegate); const plain = new Uint8Array(await crypto.subtle.digest('SHA-256', body)); assert.notDeepEqual([...plain], [...(await delegateHash(delegate))]); const prefixed = concat(Uint8Array.of(0x6e, 0x01, 0x00, 0x40), body); const manual = new Uint8Array(await crypto.subtle.digest('SHA-256', prefixed)); assert.deepEqual([...manual], [...(await delegateHash(delegate))]); }); const sig64 = new Uint8Array(64); function encoded(actions) { return encodeSignedDelegate({ ...delegate, actions }, sig64); } test('strict decoder refuses everything except one function call', () => { assert.throws(() => decodeSignedDelegate(encoded([fc, fc])), /Exactly one action/); assert.throws(() => decodeSignedDelegate(encoded([])), /Exactly one action/); assert.throws(() => decodeSignedDelegate(encoded([{ type: 'Transfer', deposit: 5n }])), /Only function calls/); assert.throws(() => decodeSignedDelegate(encoded([{ type: 'CreateAccount' }])), /Only function calls/); assert.throws(() => decodeSignedDelegate(encoded([{ type: 'AddFunctionCallKey', publicKey: pub, allowance: 1n, receiverId: 'a', methods: [] }])), /Only function calls/); assert.throws(() => decodeSignedDelegate(encoded([{ type: 'DeleteKey', publicKey: pub }])), /Only function calls/); assert.throws(() => decodeSignedDelegate(encoded([{ type: 'Delegate', delegate, signature: sig64 }])), /Only function calls/); }); test('strict decoder refuses trailing and truncated bytes', () => { const good = encoded([fc]); assert.throws(() => decodeSignedDelegate(concat(good, Uint8Array.of(0))), /Trailing/); assert.throws(() => decodeSignedDelegate(good.slice(0, good.length - 1)), /Truncated/); assert.throws(() => decodeSignedDelegate(new Uint8Array(0)), /Truncated/); }); test('strict decoder refuses oversized arguments', () => { const big = { ...fc, args: new Uint8Array(3000) }; assert.throws(() => decodeSignedDelegate(encoded([big])), /Too long/); }); test('transaction encoding has the expected layout', () => { const t = encodeTransaction({ signerId: 'a', publicKey: pub, nonce: 1n, receiverId: 'b', blockHash: new Uint8Array(32), actions: [{ type: 'Transfer', deposit: 1n }] }); // 4+1 signer, 1+32 key, 8 nonce, 4+1 receiver, 32 hash, 4 count, 1+16 transfer assert.equal(t.length, 5 + 33 + 8 + 5 + 32 + 4 + 17); assert.equal(t[5], 0); }); test('failure and event extraction', () => { const outcome = { transaction_outcome: { outcome: { status: { SuccessReceiptId: 'x' } } }, receipts_outcome: [ { outcome: { status: { SuccessValue: '' }, logs: ['hello', 'EVENT_JSON:{"standard":"duly","event":"invoice_created","data":[{"id":"1-1"}]}', 'EVENT_JSON:{bad', 'EVENT_JSON:{"standard":"nep141","event":"ft_transfer","data":[]}'] } }, { outcome: { status: { Failure: { ActionError: { kind: { FunctionCallError: { ExecutionError: 'Smart contract panicked: Nope' } } } } }, logs: [] } }, ], }; assert.deepEqual(outcomeEvents(outcome), [{ event: 'invoice_created', data: { id: '1-1' } }]); assert.ok(outcomeFailure(outcome)); }); test('toYocto', () => { assert.equal(toYocto('0.006'), 6_000_000_000_000_000_000_000n); assert.equal(toYocto('1'), 10n ** 24n); assert.ok(b64encode(Uint8Array.of(1, 2)).length > 0); });