SPB Git

Repository Public

aptos.test.ts 22.2 KB 296 lines typescript

1import { describe, expect, it } from 'vitest';2import fc from 'fast-check';3import { ed25519 } from '@noble/curves/ed25519.js';4import { hmac } from '@noble/hashes/hmac.js';5import { sha512 } from '@noble/hashes/sha2.js';6import { sha3_256 } from '@noble/hashes/sha3.js';7import {8  Account, AccountAddress, AuthenticationKey, ChainId, Deserializer, Ed25519PrivateKey, Ed25519PublicKey, Ed25519Signature, EntryFunction, RawTransaction, SignedTransaction, SimpleTransaction,9  TransactionAuthenticatorEd25519, TransactionPayloadEntryFunction, U64, generateSignedTransaction, generateSignedTransactionForSimulation, generateSigningMessageForTransaction, generateUserTransactionHash,10} from '@aptos-labs/ts-sdk';11import {12  APTOS_MAINNET_CHAIN_ID, aptosAddressOf, aptosAptStore, aptosDecodeAddress, aptosPlugin, aptosRawBytes, aptosSignedTx, aptosSimulationTx, aptosTxid, buildAptosTx, canonicalBytes, DEFAULT_POLICY,13  intentHash, isValidAddress, NETWORKS, normalizeAddress, pluginFor, toHex, type Intent, type KeyHandle, type NetworkDef,14} from '../src/index.ts';1516const net = NETWORKS.aptos as NetworkDef;17const hexToBytes = (h: string) => Uint8Array.from(Buffer.from(h.replace(/^0x/, ''), 'hex'));18const MNEMONIC = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';19// BIP-39 seed of the public test mnemonic (empty passphrase)20const SEED = hexToBytes('5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4');21// Account 0 of the public test mnemonic in Petra / @aptos-labs/ts-sdk (m/44'/637'/0'/0'/0'). The account exists on mainnet.22const VECTOR = { address: '0xeb663b681209e7087d681c5d3eed12aaa8e1915e7c87794542c3f96e94b3d3bf', publicKey: '0xa686f0309ab80312979606cfccc10ea2740147ae6888351488d11c46f08fbf60' };2324/** SLIP-0010 ed25519 (hardened only), independent of the signer's implementation. */25function slip10(seed: Uint8Array, path: string): Uint8Array {26  let I = hmac(sha512, new TextEncoder().encode('ed25519 seed'), seed);27  for (const seg of path.split('/').slice(1)) {28    if (!seg.endsWith("'")) throw new Error('hardened only');29    const data = new Uint8Array(37);30    data.set(I.subarray(0, 32), 1);31    new DataView(data.buffer).setUint32(33, (Number(seg.slice(0, -1)) | 0x80000000) >>> 0);32    I = hmac(sha512, I.subarray(32), data);33  }34  return I.slice(0, 32);35}36const PRIV = slip10(SEED, aptosPlugin.path(net));37const PUB = ed25519.getPublicKey(PRIV);38const FROM = VECTOR.address;39const TO = aptosAddressOf(ed25519.getPublicKey(slip10(SEED, "m/44'/637'/1'/0'/0'")));40const THIRD = aptosAddressOf(ed25519.getPublicKey(slip10(SEED, "m/44'/637'/2'/0'/0'")));41const key: KeyHandle = { curve: 'ed25519', publicKey: PUB, signMessage: (m) => ed25519.sign(m, PRIV) };42const sdkAccount = () => Account.fromPrivateKey({ privateKey: new Ed25519PrivateKey(PRIV, false), legacy: true });4344const now = 1_800_000_000;45const base: Intent = {46  v: 1, proposalId: '6f1c1f0e-8f5f-4b1e-9c56-0d6b5f0f1a11', keyId: 'spw-0123456789abcdef', createdAt: now, expiresAt: now + 120, policyVersion: 3,47  network: 'aptos', chainRef: '1', asset: 'native', symbol: 'APT', decimals: 8, amount: '125000000', from: FROM, to: TO, maxFee: '1000000',48  x: { sequence_number: '129', max_gas_amount: '8122', gas_unit_price: '100', expiration_timestamp_secs: String(now + 180) },49};50const withX = (x: Record<string, string>, over: Partial<Intent> = {}): Intent => ({ ...base, ...over, x: { ...base.x!, ...x } });5152/** The same transaction built by the official SDK from the same fields. */53function sdkRaw(i: Intent): RawTransaction {54  const x = i.x!;55  const payload = new TransactionPayloadEntryFunction(EntryFunction.build('0x1::aptos_account', 'transfer', [], [AccountAddress.from(i.to), new U64(BigInt(i.amount))]));56  return new RawTransaction(AccountAddress.from(i.from), BigInt(x.sequence_number!), payload, BigInt(x.max_gas_amount!), BigInt(x.gas_unit_price!), BigInt(x.expiration_timestamp_secs!), new ChainId(1));57}5859describe('aptos: registration', () => {60  it('is registered with the pinned identity, derivation, limits and two sources', () => {61    expect(pluginFor(net)).toBe(aptosPlugin);62    expect([net.family, net.id, net.chainRef, net.coinType, net.memo, net.activation, net.native.symbol, net.native.decimals]).toEqual(['aptos', 'aptos', '1', 637, undefined, undefined, 'APT', 8]);63    expect(APTOS_MAINNET_CHAIN_ID).toBe(1);64    expect(aptosPlugin.curve).toBe('ed25519');65    expect(aptosPlugin.path(net)).toBe("m/44'/637'/0'/0'/0'");66    expect(() => aptosPlugin.path({ ...net, coinType: 501 })).toThrow();67    expect(new Set(net.rpc.map((u) => new URL(u).host)).size).toBe(2);68    expect(DEFAULT_POLICY.perTx['aptos:APT']).toBe('8800000000');69    expect(DEFAULT_POLICY.daily['aptos:APT']).toBe('17600000000');70    expect(DEFAULT_POLICY.maxFee.aptos).toBe('10000000');71  });72});7374describe('aptos: keys and addresses', () => {75  it('derives the account of the public test mnemonic exactly like the official SDK (Petra path)', () => {76    expect('0x' + toHex(PUB)).toBe(VECTOR.publicKey);77    expect(aptosPlugin.address(net, PUB)).toBe(VECTOR.address);78    for (const k of [0, 1, 2]) {79      const path = `m/44'/637'/${k}'/0'/0'`;80      const sdk = Account.fromDerivationPath({ path, mnemonic: MNEMONIC, legacy: true });81      const pub = ed25519.getPublicKey(slip10(SEED, path));82      expect(toHex(pub)).toBe(toHex(sdk.publicKey.toUint8Array()));83      expect(aptosAddressOf(pub)).toBe(sdk.accountAddress.toString());84      expect(aptosAddressOf(pub)).toBe(sdk.accountAddress.toStringLong());85      // legacy Ed25519 authentication key = sha3-256(public key || 0x00); the address is that key86      expect(aptosAddressOf(pub)).toBe(AuthenticationKey.fromPublicKey({ publicKey: new Ed25519PublicKey(pub) }).derivedAddress().toStringLong());87      expect(aptosAddressOf(pub)).toBe('0x' + toHex(sha3_256(Uint8Array.from([...pub, 0]))));88    }89    expect(Account.fromDerivationPath({ path: aptosPlugin.path(net), mnemonic: MNEMONIC, legacy: true }).accountAddress.toString()).toBe(VECTOR.address);90    expect(() => aptosAddressOf(new Uint8Array(33))).toThrow();91  });92  it('accepts only the canonical long form and normalizes hexadecimal case', () => {93    expect(normalizeAddress('aptos', TO)).toBe(TO);94    expect(normalizeAddress('aptos', ` ${TO} `)).toBe(TO);95    expect(normalizeAddress('aptos', '0x' + TO.slice(2).toUpperCase())).toBe(TO);96    expect(toHex(aptosDecodeAddress(TO))).toBe(TO.slice(2));97    const bad = [98      TO.slice(2), TO.slice(0, -1), TO + '0', '0X' + TO.slice(2), TO.slice(0, -1) + 'g', '0x1', '0xa', '0x' + '0'.repeat(64), '0x' + '0'.repeat(63) + '1', '0x' + '0'.repeat(63) + 'a', '0x' + '0'.repeat(63) + 'f',99      '0x' + TO.slice(3), 'GB3JDWCQJCWMJ3IILWIGDTQJJC5567PGVEVXSCVPEQOTDN64VJBDQBYX', '0x9858EfFD232B4033E47d90003D41EC34EcaEda94', '', `${TO}\n0x`,100    ];101    for (const a of bad) expect(isValidAddress('aptos', a), a).toBe(false);102    expect(isValidAddress('aptos', '0x' + '0'.repeat(62) + '10')).toBe(true); // first address after the reserved range103    expect(() => aptosDecodeAddress('0x' + TO.slice(2).toUpperCase())).toThrow(/lowercase/); // the builder itself never normalizes104    for (const a of [TO, FROM, THIRD]) expect(AccountAddress.fromStringStrict(a).toStringLong()).toBe(a);105  });106  it('derives the primary APT store address like the framework (observed on mainnet)', () => {107    // transaction 0xbdcc8c2b…d8c9 (version 7994900991): deposit event names this store for this recipient108    expect(aptosAptStore('0xab764b960a745488b4a0fbf01f4237b7f1601332a15ef163300e9d6ff705d0db')).toBe('0xbdec6261bc2da1fd1755a9b34fd32e0e277e5d5ecf3773421c4f93f6b46fd2aa');109  });110});111112describe('aptos: transaction bytes against @aptos-labs/ts-sdk', () => {113  const variants: Intent[] = [114    base,115    withX({ sequence_number: '0', max_gas_amount: '200', gas_unit_price: '150' }, { amount: '1' }),116    withX({ sequence_number: '18446744073709551615', max_gas_amount: '2000000', gas_unit_price: '100' }, { amount: '18446744073709551615', maxFee: '200000000', to: THIRD }),117    withX({ sequence_number: '1579867', max_gas_amount: '1', gas_unit_price: '1000000' }, { amount: '8800000000', from: TO, to: FROM }),118  ];119  it('the raw transaction and signing message are byte-identical to the SDK and decode to the same fields', () => {120    for (const i of variants) {121      const b = buildAptosTx(i, net), ref = sdkRaw(i);122      expect(toHex(b.rawTx)).toBe(toHex(ref.bcsToBytes()));123      expect(toHex(b.signingMessage)).toBe(toHex(generateSigningMessageForTransaction(new SimpleTransaction(ref))));124      expect(toHex(b.signingMessage.subarray(0, 32))).toBe(toHex(sha3_256(new TextEncoder().encode('APTOS::RawTransaction'))));125      expect(aptosPlugin.build(i, net).signDocs.map(toHex)).toEqual([toHex(b.signingMessage)]);126      // decoded by the SDK: every field is the approved one, and re-encoding gives the same bytes127      const d = new Deserializer(b.rawTx), tx = RawTransaction.deserialize(d);128      expect(d.remaining()).toBe(0);129      expect(toHex(tx.bcsToBytes())).toBe(toHex(b.rawTx));130      const x = i.x!;131      expect([tx.sender.toStringLong(), tx.sequence_number, tx.max_gas_amount, tx.gas_unit_price, tx.expiration_timestamp_secs, tx.chain_id.chainId])132        .toEqual([i.from, BigInt(x.sequence_number!), BigInt(x.max_gas_amount!), BigInt(x.gas_unit_price!), BigInt(x.expiration_timestamp_secs!), 1]);133      const ef = (tx.payload as TransactionPayloadEntryFunction).entryFunction;134      expect(tx.payload).toBeInstanceOf(TransactionPayloadEntryFunction);135      expect([ef.module_name.address.toString(), ef.module_name.name.identifier, ef.function_name.identifier, ef.type_args.length, ef.args.length]).toEqual(['0x1', 'aptos_account', 'transfer', 0, 2]);136      expect(toHex(ef.args[0]!.bcsToBytes())).toBe(i.to.slice(2)); // BCS address137      expect(toHex(ef.args[1]!.bcsToBytes())).toBe(toHex(new U64(BigInt(i.amount)).bcsToBytes())); // BCS u64, little-endian138      expect(b.maxFee).toBe(BigInt(x.max_gas_amount!) * BigInt(x.gas_unit_price!));139      expect(b.describe).toMatchObject({ from: i.from, to: i.to, amountOctas: i.amount, chainId: '1', maxFeeOctas: b.maxFee.toString() });140    }141  });142  it('property: any valid field combination encodes like the SDK', () => {143    const u64 = fc.bigInt({ min: 0n, max: (1n << 64n) - 1n });144    fc.assert(fc.property(u64, fc.bigInt({ min: 1n, max: (1n << 64n) - 1n }), fc.bigInt({ min: 1n, max: 2_000_000n }), fc.bigInt({ min: 100n, max: 1_000_000n }), fc.integer({ min: 120, max: 900 }), fc.uint8Array({ minLength: 32, maxLength: 32 }),145      (seq, amount, gas, price, life, dest) => {146        const to = '0x' + toHex(dest);147        fc.pre(to !== FROM && dest.subarray(0, 31).some((v) => v !== 0));148        const i = withX({ sequence_number: seq.toString(), max_gas_amount: gas.toString(), gas_unit_price: price.toString(), expiration_timestamp_secs: String(now + life) }, { amount: amount.toString(), to, maxFee: (gas * price).toString() });149        expect(toHex(buildAptosTx(i, net).rawTx)).toBe(toHex(sdkRaw(i).bcsToBytes()));150      }), { numRuns: 300 });151  });152});153154describe('aptos: signing', () => {155  it('signs to the exact bytes the SDK produces; the SDK decodes and verifies the signature; the hash follows the Aptos rule', () => {156    for (const i of [base, withX({ sequence_number: '0' }, { amount: '1', to: THIRD })]) {157      const signed = aptosPlugin.sign(i, net, key), bytes = aptosRawBytes(signed.raw), b = buildAptosTx(i, net);158      expect(signed.raw).toMatch(/^0x[0-9a-f]+$/);159      // the SDK signing the SDK-built transaction with the same key gives the same bytes (ed25519 is deterministic)160      const transaction = new SimpleTransaction(sdkRaw(i)), account = sdkAccount();161      expect(account.accountAddress.toStringLong()).toBe(FROM);162      const senderAuthenticator = account.signTransactionWithAuthenticator(transaction);163      expect(toHex(bytes)).toBe(toHex(generateSignedTransaction({ transaction, senderAuthenticator })));164      expect(signed.txid).toBe(generateUserTransactionHash({ transaction, senderAuthenticator }));165      expect(signed.txid).toBe(aptosTxid(bytes));166      expect(signed.txid).toBe('0x' + toHex(sha3_256(Uint8Array.from([...sha3_256(new TextEncoder().encode('APTOS::Transaction')), 0, ...bytes]))));167      // decoded by the SDK: an Ed25519 authenticator carrying our key and a signature the SDK verifies over the signing message168      const d = new Deserializer(bytes), st = SignedTransaction.deserialize(d);169      expect(d.remaining()).toBe(0);170      expect(toHex(st.raw_txn.bcsToBytes())).toBe(toHex(b.rawTx));171      expect(st.authenticator).toBeInstanceOf(TransactionAuthenticatorEd25519);172      const auth = st.authenticator as TransactionAuthenticatorEd25519;173      expect(toHex(auth.public_key.toUint8Array())).toBe(toHex(PUB));174      expect(auth.public_key.verifySignature({ message: b.signingMessage, signature: auth.signature })).toBe(true);175      expect(auth.public_key.verifySignature({ message: generateSigningMessageForTransaction(transaction), signature: new Ed25519Signature(auth.signature.toUint8Array()) })).toBe(true);176      expect(ed25519.verify(auth.signature.toUint8Array(), b.signingMessage, PUB)).toBe(true);177      expect(auth.public_key.verifySignature({ message: buildAptosTx({ ...i, amount: '2' }, net).signingMessage, signature: auth.signature })).toBe(false);178      expect(toHex(aptosSignedTx(b.rawTx, PUB, auth.signature.toUint8Array()))).toBe(toHex(bytes));179    }180  });181  it('builds the simulation payloads like the SDK (zeroed signature, or no authenticator when the key is unknown)', async () => {182    const transaction = new SimpleTransaction(sdkRaw(base)), raw = buildAptosTx(base, net).rawTx;183    const zeroed = aptosSimulationTx(raw, PUB);184    expect(toHex(zeroed)).toBe(toHex(await generateSignedTransactionForSimulation({ transaction, signerPublicKey: new Ed25519PublicKey(PUB) })));185    expect(toHex(zeroed.subarray(zeroed.length - 64))).toBe('00'.repeat(64));186    expect(ed25519.verify(zeroed.subarray(zeroed.length - 64), buildAptosTx(base, net).signingMessage, PUB)).toBe(false); // can never be submitted187    expect(toHex(aptosSimulationTx(raw))).toBe(toHex(await generateSignedTransactionForSimulation({ transaction })));188    expect(toHex(aptosSimulationTx(raw))).toBe(toHex(raw) + '0404');189  });190  it('refuses a key that does not control the source, the wrong curve, and a bad signature', () => {191    const other = slip10(SEED, "m/44'/637'/1'/0'/0'");192    expect(() => aptosPlugin.sign(base, net, { curve: 'ed25519', publicKey: ed25519.getPublicKey(other), signMessage: (m) => ed25519.sign(m, other) })).toThrow(/does not control/);193    expect(() => aptosPlugin.sign(base, net, { curve: 'secp256k1', publicKey: new Uint8Array(33), signDigest: () => { throw new Error('no'); } })).toThrow(/ed25519/);194    expect(() => aptosPlugin.sign(base, net, { curve: 'ed25519', publicKey: PUB })).toThrow(/ed25519/);195    expect(() => aptosPlugin.sign(base, net, { curve: 'ed25519', publicKey: PUB, signMessage: (m) => ed25519.sign(m, other) })).toThrow(/self-check/);196    expect(() => aptosRawBytes('0x00')).toThrow();197    expect(() => aptosRawBytes(aptosPlugin.sign(base, net, key).raw.slice(2))).toThrow();198  });199});200201describe('aptos: fail closed', () => {202  const bad: [string, Intent, RegExp][] = [203    ['testnet chain id in the intent', { ...base, chainRef: '2' }, /identity/],204    ['another network id', { ...base, network: 'solana' }, /not an Aptos intent/],205    ['token asset', { ...base, asset: '0xbae207659db88bea0cbead6da0ed00aac12edcdda169e591cd41c94180b46f3b' }, /native/],206    ['coin type as asset', { ...base, asset: '0x1::aptos_coin::AptosCoin' }, /native/],207    ['wrong symbol', { ...base, symbol: 'USDC' }, /metadata/],208    ['wrong decimals', { ...base, decimals: 6 }, /metadata/],209    ['amount zero', { ...base, amount: '0' }, /positive/],210    ['amount above u64', { ...base, amount: '18446744073709551616' }, /range/],211    ['amount not canonical', { ...base, amount: '0125' }, /base-unit/],212    ['amount negative', { ...base, amount: '-5' }, /base-unit/],213    ['destination equals source', { ...base, to: FROM }, /equals source/],214    ['upper-case destination', { ...base, to: '0x' + TO.slice(2).toUpperCase() }, /invalid Aptos address/],215    ['destination without leading zeros restored', { ...base, to: '0x' + TO.slice(3) }, /invalid Aptos address/],216    ['destination without 0x', { ...base, to: TO.slice(2) }, /invalid Aptos address/],217    ['framework address as destination', { ...base, to: '0x' + '0'.repeat(63) + '1' }, /reserved/],218    ['APT metadata object as destination', { ...base, to: '0x' + '0'.repeat(63) + 'a' }, /reserved/],219    ['invalid source', { ...base, from: FROM.slice(0, -1) + 'z' }, /invalid Aptos address/],220    ['reserved source', { ...base, from: '0x' + '0'.repeat(64) }, /reserved/],221    ['memo', { ...base, memo: 'hello' }, /no memo/],222    ['fee above maxFee', withX({ max_gas_amount: '10001' }), /above approved maximum/],223    ['fee above maxFee through the price', withX({ gas_unit_price: '124' }), /above approved maximum/],224    ['gas price below the network minimum', withX({ gas_unit_price: '99' }), /range/],225    ['gas price zero', withX({ gas_unit_price: '0' }), /range/],226    ['gas price above u64', withX({ gas_unit_price: '18446744073709551616' }, { maxFee: '9'.repeat(30) }), /range/],227    ['gas limit zero', withX({ max_gas_amount: '0' }), /range/],228    ['gas limit above the network maximum', withX({ max_gas_amount: '2000001' }, { maxFee: '300000000' }), /range/],229    ['gas limit not canonical', withX({ max_gas_amount: '08122' }), /canonical/],230    ['gas limit negative', withX({ max_gas_amount: '-1' }), /canonical/],231    ['sequence above u64', withX({ sequence_number: '18446744073709551616' }), /range/],232    ['sequence not canonical', withX({ sequence_number: '0x81' }), /canonical/],233    ['sequence empty', withX({ sequence_number: '' }), /canonical/],234    ['expiration zero', withX({ expiration_timestamp_secs: '0' }), /range/],235    ['expiration in the past', withX({ expiration_timestamp_secs: String(now) }), /not after/],236    ['expiration before the approval window ends', withX({ expiration_timestamp_secs: String(now + 119) }), /before the approval window/],237    ['expiration too far away', withX({ expiration_timestamp_secs: String(now + 901) }), /too far/],238    ['unknown x key', withX({ payload: '00' }), /unknown or missing/],239    ['another function', withX({ function: '0x1::coin::transfer' }), /unknown or missing/],240    ['chain id override', withX({ chain_id: '1' }), /unknown or missing/],241    ['missing x key', { ...base, x: { sequence_number: '1', max_gas_amount: '200', gas_unit_price: '100' } }, /unknown or missing/],242    ['renamed x key', { ...base, x: { sequenceNumber: '1', max_gas_amount: '200', gas_unit_price: '100', expiration_timestamp_secs: String(now + 180) } }, /unknown or missing/],243    ['no x section', (() => { const { x: _x, ...r } = base; return r as Intent; })(), /missing Aptos parameters/],244    ['foreign chain section', { ...base, evm: { nonce: 0, gasLimit: '21000', maxFeePerGas: '1', maxPriorityFeePerGas: '1' } }, /unexpected chain section/],245    ['unsupported intent version', { ...base, v: 2 as never }, /version/],246  ];247  for (const [name, i, re] of bad) it(name, () => {248    expect(() => aptosPlugin.build(i, net)).toThrow(re);249    expect(() => aptosPlugin.sign(i, net, key)).toThrow(re);250  });251  it('refuses a network definition that is not the pinned main network', () => {252    expect(() => aptosPlugin.build({ ...base, chainRef: '2' }, { ...net, chainRef: '2' })).toThrow(/main network/);253    expect(() => aptosPlugin.build(base, { ...net, family: 'evm' })).toThrow(/not an Aptos intent/);254    expect(() => aptosPlugin.build({ ...base, network: 'aptos2' }, { ...net, id: 'aptos2' })).not.toThrow(); // the id is only a registry key; the chain id is what is signed255    expect(() => aptosPlugin.build({ ...base, symbol: 'ABC' }, { ...net, native: { ...net.native, symbol: 'ABC', id: 'ABC' } })).toThrow(/native/);256    expect(() => aptosPlugin.build({ ...base, decimals: 6 }, { ...net, native: { ...net.native, decimals: 6 } })).toThrow(/metadata/);257  });258  it('the intent schema and hash accept the Aptos section and bind every field', () => {259    expect(() => canonicalBytes(base)).not.toThrow();260    expect(() => canonicalBytes((() => { const { x: _x, ...r } = base; return r as Intent; })())).toThrow(/does not match network/);261    expect(() => canonicalBytes({ ...base, x: { ...base.x!, 'bad key': '1' } })).toThrow();262  });263  it('changing any intent field changes the signed bytes or throws', () => {264    const ref = toHex(aptosPlugin.build(base, net).signDocs[0]!);265    const txMuts: Intent[] = [266      { ...base, amount: '125000001' }, { ...base, to: THIRD }, { ...base, from: THIRD },267      withX({ sequence_number: '130' }), withX({ max_gas_amount: '8123' }), withX({ gas_unit_price: '101' }), withX({ expiration_timestamp_secs: String(now + 181) }),268    ];269    const seen = new Set([ref]);270    for (const m of txMuts) seen.add(toHex(aptosPlugin.build(m, net).signDocs[0]!));271    expect(seen.size).toBe(txMuts.length + 1);272    const signedRef = aptosPlugin.sign(base, net, key);273    for (const m of txMuts.filter((t) => t.from === FROM)) {274      const s = aptosPlugin.sign(m, net, key);275      expect(s.raw).not.toBe(signedRef.raw);276      expect(s.txid).not.toBe(signedRef.txid);277    }278    // fields that are not part of the Aptos transaction either throw or leave it unchanged, and always change the approved intent hash279    const other: Intent[] = [280      { ...base, chainRef: '2' }, { ...base, network: 'solana' }, { ...base, asset: 'other' }, { ...base, symbol: 'APTT' }, { ...base, decimals: 9 },281      { ...base, maxFee: '812199' }, { ...base, createdAt: now + 200 }, { ...base, expiresAt: now + 181 }, { ...base, v: 2 as never }, { ...base, memo: '1' },282      { ...base, maxFee: '1000001' }, { ...base, proposalId: '6f1c1f0e-8f5f-4b1e-9c56-0d6b5f0f1a12' }, { ...base, keyId: 'spw-0123456789abcdee' }, { ...base, policyVersion: 4 },283    ];284    const h0 = toHex(intentHash(base));285    other.forEach((m, k) => {286      let out: string | null = null;287      try { out = toHex(aptosPlugin.build(m, net).signDocs[0]!); } catch { /* refused */ }288      if (k < 10) expect(out, `mutation ${k}`).toBeNull(); else expect(out).toBe(ref);289      let h: string | null = null;290      try { h = toHex(intentHash(m)); } catch { /* refused by the schema */ }291      expect(h).not.toBe(h0);292    });293    for (const m of txMuts) expect(toHex(intentHash(m))).not.toBe(h0);294  });295});296
Files main
Menu

Theme