@integraledger/lcp-binding-aptos
v0.20.1
Published
Welds an ATR hash into an Aptos settlement.
Readme
@integraledger/lcp-binding-aptos
Welds an ATR hash into an Aptos settlement.
npm install @integraledger/lcp-binding-aptos| | |
|---|---|
| Chain | Aptos |
| Pattern | overlay-contract |
| Carrier | a Move entry-call argument |
| Surface | createAptosAdapter returning a rail-native adapter over a AptosReader port — not @integraledger/lcp-binding-core's WeldAdapter, whose shape is EVM's |
This package has no chain SDK. Its only I/O is one GET against an Aptos fullnode's REST API, so
makeAptosReader takes the fullnode URL rather than a client. It carries an API key, arbitrary headers,
a per-attempt deadline and a bounded jittered retry; fetch, the sleep and the randomness are injectable.
import {
APTOS_MANIFEST,
createAptosAdapter,
getAptosConfig,
makeAptosReader,
} from "@integraledger/lcp-binding-aptos";
declare const atrHash: string;
declare const recipient: string;
declare const hash: string;
declare const apiKey: string | undefined; // e.g. process.env.APTOS_API_KEY
const adapter = createAptosAdapter(APTOS_MANIFEST, "testnet");
// PAYER — the Move entry call to sign. `payment_id` carries the atrHash.
const call = adapter.propose({ atrHash, recipient, amount: 1_000_000n });
// VERIFIER — the atrHash back out of a settled transaction's `PaymentSettled` event.
const reader = makeAptosReader({
fullnodeUrl: getAptosConfig("testnet").fullnodeUrl,
apiKey, // `Authorization: Bearer <key>` — never logged, never in an error
timeoutMs: 10_000, // per attempt, body included
retry: { attempts: 3 }, // 408/429/5xx, transport failures and deadlines; honours `Retry-After`
});
const recovered = await adapter.recover({ hash }, reader);
if (!("refused" in recovered)) console.log(recovered.value); // "0x…"
// Narrow with `"refused" in x` — `Refusal` has no `ok`, so the union cannot discriminate on one.txView answers null for a hash the fullnode does not have and throws AptosReadError for
everything else: AptosFullnodeError when the node answered (a 429, a 500, a body that is not a JSON
object) and AptosTransportError when there was no answer to have a status (a refused connection, an
elapsed deadline). The two endings are different facts — there is no such transaction versus we could
not look — and collapsing them would let an outage read as a settlement that never happened. One
catch (e) { if (e instanceof AptosReadError) … } keeps them apart.
Retry is on the read path only. A read is idempotent, so a 408, a 429, a 5xx, a refused connection or
an elapsed deadline is retried within a bounded budget with full jitter, obeying Retry-After up to a
cap. A 404 is never retried — it is an answer. A 400, a 401 or a 403 is never retried either. ⛔ This
package submits nothing: a retried POST /transactions can spend the gas twice, and there is no safe
default for that.
⛔ HTTP 404 is not the test for absence. A live Aptos fullnode answers 404 both for a transaction it
does not have (error_code: "transaction_not_found") and for a path it does not have
(error_code: "web_framework_error", which is what a typo'd fullnodeUrl produces). Only the first is
null; every other 404, including one with an empty or unparseable body, throws — because a
misconfigured URL answering "not settled" for every settlement is silent and permanent, not transient.
AptosFullnodeError.errorCode carries which one it was. ⚠️ "The fullnode has no such transaction" is a statement about
that fullnode: a pruning node is expected to answer transaction_not_found for a settlement older than
its ledger window, so recover old settlements through an archival node or an indexer.
A configured secret never reaches an error. apiKey and every value in secretHeaders are redacted
from the response body an error carries, the request URL appears in no message, and a transport failure
carries the runtime's symbolic code (ECONNREFUSED) rather than the underlying error or its text.
getAptosConfig("mainnet") throws: the lcp_payment module is unpublished there, and a 0x0 module
address would send a settlement to a call target that does not exist.
The carrier — and why this one is an overlay
The ATR hash rides the payment_id: vector<u8> argument of a Move entry call. That call targets a
bespoke, deployed lcp_payment module rather than a stock Aptos primitive, which is precisely what
makes this an overlay-contract binding rather than a native-field one.
The distinction is not bookkeeping. Aptos's stock USDC and APT coins carry no arbitrary-bytes field, so a settlement can only bind an ATR hash through a module someone deployed — and a binding that depends on a deployed contract inherits that contract's trust assumptions, where a native-field binding inherits only the chain's.
import {
decodePaymentIdBytes,
encodePaymentId,
encodePaymentIdArg,
} from "@integraledger/lcp-binding-aptos";
declare const atrHash: string;
const paymentId = encodePaymentId(atrHash); // Uint8Array(32) — throws on a malformed atrHash
encodePaymentIdArg(atrHash); // number[] — the shape a Move entry call's `functionArguments` wants
decodePaymentIdBytes(paymentId); // "0x…" | nullThe module address is per-deployment, so it is configuration rather than a constant.
Requirement ids
This package's source and its messages cite short ids — ATA-3, RCS-5, CMP-6 and their kin.
They are not LCP clause numbers. LCP is cited by section (§8.3.1, §C.2); anything shaped XXX-n
comes from Integra's functional specification of what a complete agent transaction requires, the fourteen
families below. Nothing in this package's behaviour depends on them, and where an id and an LCP section
disagree the section governs.
| | | | |
|---|---|---|---|
| IDN identity | ASP authority to spend | ATA authority to accept terms | TRM the terms record |
| RCS recourse | PAY payment and settlement | WLD the transactional weld | OFR offer integrity |
| FRC fraud, risk, and compliance | OPS commercial operations | DSC discovery and reputation | ORC orchestration |
| CMP composition | PRS persistence and verification infrastructure | | |
Requires Node >= 24. Part of the Legal Context Protocol open layer — see the documentation and the package index. Apache-2.0.
