Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ Resource getters are lazy and call `requireServiceApiKey`:
| --------------- | -------------- | ------------------------------------------------------------- |
| `.environments` | `Environments` | `list()`, `get(id)`, `create(input)`, `delete(id)` |
| `.wallets` | `Wallets` | `list({ environmentId })`, `get(id)`, `create(input)`, `delete(id)` |
| `.transactions` | `Transactions` | `createReceive(input)`, `send(params)`, `prepareSend(params)`, `isSendReady(walletId)`, `forgetSend(walletId)` |
| `.transactions` | `Transactions` | `findOne(id)`, `findMany(params)`, `createReceive(input)`, `send(params)`, `prepareSend(params)`, `isSendReady(walletId)`, `forgetSend(walletId)` |
| `.webhooks` | `Webhooks` | `verify(input)` — does NOT require any API key |

`Payments.webhooks` is also a static reference to `Webhooks` for stateless use.
Expand Down
16 changes: 16 additions & 0 deletions docs/INTEGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,22 @@ Three things to plan around:
rotated node credentials — there is no expiry. The Argon2 master key is never
cached; only the one wallet's macaroon is.

### Looking up transactions

```ts
const transaction = await payments.transactions.findOne(transactionId);

const { list, pagination, total_count } = await payments.transactions.findMany({
wallet_id: walletId,
filter: { status: 'COMPLETED', direction: 'SEND' }, // optional
page: { limit: 20, offset: 0 }, // optional
});
```

`findOne` returns the full transaction record. `findMany` returns trimmed
records (no `payment_hash` / `payment_request` / `error`) — use `findOne` when
you need those fields for one transaction.

## Step 5 — Consume webhooks

Amboss signs every webhook: HMAC-SHA256 over `${timestamp}.${rawBody}`, sent
Expand Down
13 changes: 13 additions & 0 deletions packages/payments/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,19 @@ await payments.wallets.delete(id);

### Transactions

#### Finding transactions

```ts
await payments.transactions.findOne(id); // full transaction record
await payments.transactions.findMany({ wallet_id: walletId }); // { list, pagination, total_count }
```

`findMany` accepts the same input as the backend's transaction list query —
`environment_id`, `wallet_id`, `page: { limit, offset }`, and
`filter: { date_range, direction, status }`. Each item in `list` is a trimmed
record (no `payment_hash` / `payment_request` / `error` / etc.); use `findOne`
for the full transaction.

#### Receiving

`transactions.createReceive` generates a Lightning invoice for a wallet. Unlike
Expand Down
82 changes: 82 additions & 0 deletions packages/payments/src/generated/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1587,6 +1587,24 @@ export type DeleteEnvironmentMutation = { payment: { environment: { delete: bool

export type PaymentsTransactionFieldsFragment = { id: string, wallet_id: string, node_id?: string | null, idempotency_key: string, direction: PaymentsTransactionDirection, status: PaymentsTransactionStatus, amount_sats?: string | null, fee?: string | null, payment_hash?: string | null, payment_request?: string | null, description?: string | null, error?: string | null, expires_at?: string | null, settled_at?: string | null, created_at: string, updated_at: string, amount: { id: string, display_amount: string, full_amount: string }, asset: { id: string, symbol: string, type: BitcoinAssetType, precision: number } };

export type SimplePaymentsTransactionFieldsFragment = { id: string, wallet_id: string, direction: PaymentsTransactionDirection, status: PaymentsTransactionStatus, fee?: string | null, description?: string | null, settled_at?: string | null, created_at: string, updated_at: string, amount: { id: string, display_amount: string, full_amount: string }, asset: { id: string, symbol: string, type: BitcoinAssetType, precision: number } };

export type FindManyTransactionsFieldsFragment = { total_count: number, list: Array<{ id: string, wallet_id: string, direction: PaymentsTransactionDirection, status: PaymentsTransactionStatus, fee?: string | null, description?: string | null, settled_at?: string | null, created_at: string, updated_at: string, amount: { id: string, display_amount: string, full_amount: string }, asset: { id: string, symbol: string, type: BitcoinAssetType, precision: number } }>, pagination: { limit: number, offset: number } };

export type GetTransactionQueryVariables = Exact<{
id: Scalars['String']['input'];
}>;


export type GetTransactionQuery = { payment: { transaction: { find_one: { id: string, wallet_id: string, node_id?: string | null, idempotency_key: string, direction: PaymentsTransactionDirection, status: PaymentsTransactionStatus, amount_sats?: string | null, fee?: string | null, payment_hash?: string | null, payment_request?: string | null, description?: string | null, error?: string | null, expires_at?: string | null, settled_at?: string | null, created_at: string, updated_at: string, amount: { id: string, display_amount: string, full_amount: string }, asset: { id: string, symbol: string, type: BitcoinAssetType, precision: number } } } } };

export type ListTransactionsQueryVariables = Exact<{
input: ListTransactionsInput;
}>;


export type ListTransactionsQuery = { payment: { transaction: { find_many: { total_count: number, list: Array<{ id: string, wallet_id: string, direction: PaymentsTransactionDirection, status: PaymentsTransactionStatus, fee?: string | null, description?: string | null, settled_at?: string | null, created_at: string, updated_at: string, amount: { id: string, display_amount: string, full_amount: string }, asset: { id: string, symbol: string, type: BitcoinAssetType, precision: number } }>, pagination: { limit: number, offset: number } } } } };

export type CreateReceiveTransactionMutationVariables = Exact<{
input: CreateReceiveTransactionInput;
}>;
Expand Down Expand Up @@ -1689,6 +1707,42 @@ export const PaymentsTransactionFieldsFragmentDoc = `
updated_at
}
`;
export const SimplePaymentsTransactionFieldsFragmentDoc = `
fragment SimplePaymentsTransactionFields on SimplePaymentsTransaction {
id
wallet_id
direction
status
amount {
id
display_amount
full_amount
}
asset {
id
symbol
type
precision
}
fee
description
settled_at
created_at
updated_at
}
`;
export const FindManyTransactionsFieldsFragmentDoc = `
fragment FindManyTransactionsFields on FindManyTransactions {
list {
...SimplePaymentsTransactionFields
}
pagination {
limit
offset
}
total_count
}
${SimplePaymentsTransactionFieldsFragmentDoc}`;
export const PaymentsEnvironmentFieldsFragmentDoc = `
fragment PaymentsEnvironmentFields on PaymentsEnvironment {
id
Expand Down Expand Up @@ -1805,6 +1859,28 @@ export const DeleteEnvironmentDocument = `
}
}
`;
export const GetTransactionDocument = `
query GetTransaction($id: String!) {
payment {
transaction {
find_one(id: $id) {
...PaymentsTransactionFields
}
}
}
}
${PaymentsTransactionFieldsFragmentDoc}`;
export const ListTransactionsDocument = `
query ListTransactions($input: ListTransactionsInput!) {
payment {
transaction {
find_many(input: $input) {
...FindManyTransactionsFields
}
}
}
}
${FindManyTransactionsFieldsFragmentDoc}`;
export const CreateReceiveTransactionDocument = `
mutation CreateReceiveTransaction($input: CreateReceiveTransactionInput!) {
payment {
Expand Down Expand Up @@ -1947,6 +2023,12 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper =
DeleteEnvironment(variables: DeleteEnvironmentMutationVariables, requestHeaders?: GraphQLClientRequestHeaders, signal?: RequestInit['signal']): Promise<DeleteEnvironmentMutation> {
return withWrapper((wrappedRequestHeaders) => client.request<DeleteEnvironmentMutation>({ document: DeleteEnvironmentDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), 'DeleteEnvironment', 'mutation', variables);
},
GetTransaction(variables: GetTransactionQueryVariables, requestHeaders?: GraphQLClientRequestHeaders, signal?: RequestInit['signal']): Promise<GetTransactionQuery> {
return withWrapper((wrappedRequestHeaders) => client.request<GetTransactionQuery>({ document: GetTransactionDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), 'GetTransaction', 'query', variables);
},
ListTransactions(variables: ListTransactionsQueryVariables, requestHeaders?: GraphQLClientRequestHeaders, signal?: RequestInit['signal']): Promise<ListTransactionsQuery> {
return withWrapper((wrappedRequestHeaders) => client.request<ListTransactionsQuery>({ document: ListTransactionsDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), 'ListTransactions', 'query', variables);
},
CreateReceiveTransaction(variables: CreateReceiveTransactionMutationVariables, requestHeaders?: GraphQLClientRequestHeaders, signal?: RequestInit['signal']): Promise<CreateReceiveTransactionMutation> {
return withWrapper((wrappedRequestHeaders) => client.request<CreateReceiveTransactionMutation>({ document: CreateReceiveTransactionDocument, variables, requestHeaders: { ...requestHeaders, ...wrappedRequestHeaders }, signal }), 'CreateReceiveTransaction', 'mutation', variables);
},
Expand Down
54 changes: 54 additions & 0 deletions packages/payments/src/operations/transactions.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,60 @@ fragment PaymentsTransactionFields on PaymentsTransaction {
updated_at
}

fragment SimplePaymentsTransactionFields on SimplePaymentsTransaction {
id
wallet_id
direction
status
amount {
id
display_amount
full_amount
}
asset {
id
symbol
type
precision
}
fee
description
settled_at
created_at
updated_at
}

fragment FindManyTransactionsFields on FindManyTransactions {
list {
...SimplePaymentsTransactionFields
}
pagination {
limit
offset
}
total_count
}

query GetTransaction($id: String!) {
payment {
transaction {
find_one(id: $id) {
...PaymentsTransactionFields
}
}
}
}

query ListTransactions($input: ListTransactionsInput!) {
payment {
transaction {
find_many(input: $input) {
...FindManyTransactionsFields
}
}
}
}

mutation CreateReceiveTransaction($input: CreateReceiveTransactionInput!) {
payment {
transaction {
Expand Down
65 changes: 65 additions & 0 deletions packages/payments/src/resources/transactions.find.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';

import type { GraphQLClient } from 'graphql-request';

import { Transactions } from './transactions.js';

/** Fake GraphQLClient that answers GetTransaction / ListTransactions. */
function fakeClient(): { client: GraphQLClient; ops: string[] } {
const ops: string[] = [];
const request = async ({ document }: { document: string }): Promise<unknown> => {
ops.push(document);
if (document.includes('GetTransaction')) {
return {
payment: {
transaction: {
find_one: { id: 'tx1', wallet_id: 'w1', status: 'COMPLETED' },
},
},
};
}
if (document.includes('ListTransactions')) {
return {
payment: {
transaction: {
find_many: {
list: [{ id: 'tx1', wallet_id: 'w1', status: 'COMPLETED' }],
pagination: { limit: 20, offset: 0 },
total_count: 1,
},
},
},
};
}
throw new Error(`unexpected document: ${document.slice(0, 40)}`);
};
return { client: { request } as unknown as GraphQLClient, ops };
}

describe('Transactions.findOne', () => {
it('calls GetTransaction and returns the transaction', async () => {
const { client, ops } = fakeClient();
const transactions = new Transactions(client);

const result = await transactions.findOne('tx1');

assert.equal(result.id, 'tx1');
assert.ok(ops.some((document) => document.includes('GetTransaction')));
});
});

describe('Transactions.findMany', () => {
it('calls ListTransactions and returns list, pagination, and total_count', async () => {
const { client, ops } = fakeClient();
const transactions = new Transactions(client);

const result = await transactions.findMany({ wallet_id: 'w1' });

assert.equal(result.total_count, 1);
assert.equal(result.pagination.limit, 20);
assert.equal(result.list.length, 1);
assert.equal(result.list[0]?.id, 'tx1');
assert.ok(ops.some((document) => document.includes('ListTransactions')));
});
});
12 changes: 12 additions & 0 deletions packages/payments/src/resources/transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
getSdk,
type CreateReceiveTransactionInput,
type CreateSendTransactionInput,
type FindManyTransactionsFieldsFragment,
type ListTransactionsInput,
type PaymentsTransactionFieldsFragment,
} from '../generated/sdk.js';
import { sendAssetPayment } from '../node/lit.js';
Expand Down Expand Up @@ -145,6 +147,16 @@ export class Transactions {
this.#pending.delete(walletId);
}

async findOne(id: string): Promise<PaymentsTransactionFieldsFragment> {
const res = await this.#sdk.GetTransaction({ id });
return res.payment.transaction.find_one;
}

async findMany(input: ListTransactionsInput): Promise<FindManyTransactionsFieldsFragment> {
const res = await this.#sdk.ListTransactions({ input });
return res.payment.transaction.find_many;
}

async createReceive(
input: CreateReceiveTransactionInput,
): Promise<PaymentsTransactionFieldsFragment> {
Expand Down
Loading